0

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];
    }
}
twernt
  • 20,271
  • 5
  • 32
  • 41
SleepNot
  • 2,982
  • 10
  • 43
  • 72

1 Answers1

0

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.

Nikolay Mamaev
  • 1,474
  • 1
  • 12
  • 21
  • That is where I actually based my code on. Problem is the hitTest event fires up twice and also doesn't seem to do what I need which is to allow user to cancel firing a button's method upon dragging his finger outside the button to cancel just like a default UIButton with TouchUpInside event would do. – SleepNot Jun 14 '14 at 05:25
  • I tested your code and can't reproduce your issue with calling of the touchesCancelled method. Could you please clarify scenario? also it would be fine if you provide the whole code for your child of UIButton class. – Nikolay Mamaev Jun 14 '14 at 07:03
  • Please see my edit. Issue is if I drag within the UIBUtton, it calls the touchesCancelled event. – SleepNot Jun 14 '14 at 07:07
  • Sorry, still can't catch calling touchesCancelled method. But I observed another bug: if touch down within buttonView.frame but outside of area limited by Bezier path, then move finger inside the Bezier path area and touch up there, then UIControlEventTouchUpInside fires. That is, it looks like you start dragging out of button and end dragging within button's area; in this case touch up action shouldn't fire but it nevertheless fires. The fix for this is to add `hitTest:withEvent:` method implementation in addition to your touchesBegan, touchesMoved, touchesEnded & touchesCancelled methods. – Nikolay Mamaev Jun 14 '14 at 07:50
  • That is, please try to add the following code to your class: `- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if ([_path containsPoint:point]) { return self; } else { return nil; } }` – Nikolay Mamaev Jun 14 '14 at 07:51
  • I see. Thanks for pointing out that bug. I will try to implement hitTest:withEvent: in my code. – SleepNot Jun 14 '14 at 07:52