Implement touch cancel on UIButton

676 views Asked by At

I have a subclassed UIButton that I made into an irregular shape (Parallelogram) where I override the touch events so it will only accept touch events inside the shape

How can I implement a touch event like a normal UIButton where I can cancel a touch event upon tapping and dragging the finger outside the UIButton to cancel a touch. For my current code, If I drag my finger inside the button, it calls the touchesCancelled event. I am using the TouchUpInside event for performing methods in the UIButton. Here is my code:

- (id)initWithFrame:(CGRect)frame withAngle:(AngleType)angle andColor:(UIColor *)color
{
    if ((self = [super initWithFrame:frame])){

        [self setImage:[Utils imageWithColor:color andSize:frame.size] forState:UIControlStateNormal];

        _path = [UIBezierPath new];

        self.angleType = angle;

        switch (angle) {
            case AngleLeft:
            {
                [_path moveToPoint:CGPointMake(0, elementHeight(self))];
                [_path addLineToPoint:CGPointMake(elementWidth(self), elementHeight(self))];
                [_path addLineToPoint:CGPointMake(elementWidth(self), 0)];
                [_path addLineToPoint:CGPointMake(15, 0)];
                [_path addLineToPoint:CGPointMake(0, elementHeight(self))];
            }
                break;
            case AngleRight:
            {
                [_path moveToPoint:CGPointMake(0, 0)];
                [_path addLineToPoint:CGPointMake(0, elementHeight(self))];
                [_path addLineToPoint:CGPointMake(elementWidth(self),elementHeight(self))];
                [_path addLineToPoint:CGPointMake(elementWidth(self) - 15, 0)];
                [_path addLineToPoint:CGPointMake(0, 0)];
            }
                break;
            case AngleBoth:
            {
                [_path moveToPoint:CGPointMake(15, 0)];
                [_path addLineToPoint:CGPointMake(0, elementHeight(self))];
                [_path addLineToPoint:CGPointMake(elementWidth(self) - 15, elementHeight(self))];
                [_path addLineToPoint:CGPointMake(elementWidth(self), 0)];
                [_path addLineToPoint:CGPointMake(15, 0)];
            }
                break;
            default:
                break;
        }

        CAShapeLayer *mask = [CAShapeLayer new];
        mask.frame = self.bounds;
        mask.path = _path.CGPath;

        self.layer.mask = mask;

    }

    return self;
}

- (void)setText:(NSString *)text withAlignment:(NSTextAlignment)alignment
{
    _buttonLabel = [[UILabel alloc] init];
    _buttonLabel.font = FONT_Helvetica_Neue(14);
    _buttonLabel.textColor = [UIColor whiteColor];
    _buttonLabel.text = text;
    _buttonLabel.textAlignment = alignment;

    if (alignment == NSTextAlignmentLeft) {
        _buttonLabel.frame = CGRectMake(15 + 10, 0, elementWidth(self), elementHeight(self));
    } else if (alignment == NSTextAlignmentRight) {
        _buttonLabel.frame = CGRectMake(0, 0, elementWidth(self) - 15 - 10, elementHeight(self));
    } else if (alignment == NSTextAlignmentCenter) {
        _buttonLabel.frame = CGRectMake(0, 0, elementWidth(self), elementHeight(self));
    }

    [self addSubview:_buttonLabel];
}

- (void)setBackgroundColor:(UIColor *)backgroundColor
{
    [self setImage:[Utils imageWithColor:backgroundColor andSize:self.frame.size] forState:UIControlStateNormal];

    CAShapeLayer *mask = [CAShapeLayer new];
    mask.frame = self.bounds;
    mask.path = _path.CGPath;

    self.layer.mask = mask;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self];

    if ([_path containsPoint:touchLocation]) {
        NSLog(@"Inside!");
        self.highlighted = YES;
        //[self sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self];

    self.highlighted = NO;
    if ([_path containsPoint:touchLocation]) {
        [self sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self];

    if ([_path containsPoint:touchLocation]) {
        NSLog(@"...");
        self.highlighted = YES;
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self];

    self.highlighted = NO;
    if ([_path containsPoint:touchLocation]) {
        [self sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}
1

There are 1 answers

6
Nikolay Mamaev On

Instead of trying to implement custom touch events handling, please override the hitTest:withEvent: method. Please refer to this tutorial which I believe describes your case. And this is another example.