在 UIButton 上实现触摸取消

Posted

技术标签:

【中文标题】在 UIButton 上实现触摸取消【英文标题】:Implement touch cancel on UIButton 【发布时间】:2014-06-14 04:44:45 【问题描述】:

我有一个子类 UIButton,我将它制作成不规则形状(平行四边形),我在其中覆盖了触摸事件,因此它只接受形状内的触摸事件

如何实现像普通 UIButton 这样的触摸事件,在其中我可以在点击并拖动 UIButton 外的手指以取消触摸时取消触摸事件。对于我当前的代码,如果我在按钮内拖动手指,它会调用 touchesCancelled 事件。我正在使用 TouchUpInside 事件来执行 UIButton 中的方法。这是我的代码:

- (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】:

请覆盖hitTest:withEvent: 方法,而不是尝试实现自定义触摸事件处理。请参阅this tutorial,我相信它描述了您的情况。还有this is 另一个例子。

【讨论】:

那是我的代码的实际基础。问题是 hitTest 事件触发了两次,而且似乎也没有做我需要的事情,即允许用户在将手指拖到按钮外以取消触发按钮的方法时取消,就像带有 TouchUpInside 事件的默认 UIButton 会做的那样。 我测试了您的代码,但无法通过调用 touchesCancelled 方法重现您的问题。你能澄清一下场景吗?如果您为您的 UIButton 类的孩子提供整个代码,那也很好。 请看我的编辑。问题是如果我在 UIBUtton 内拖动,它会调用 touchesCancelled 事件。 抱歉,仍然无法捕捉到调用 touchesCancelled 方法。但我观察到另一个错误:如果在 buttonView.frame 内但在 Bezier 路径限制的区域之外触摸,则在 Bezier 路径区域内移动手指并向上触摸,然后 UIControlEventTouchUpInside 触发。也就是说,看起来您开始拖出按钮并在按钮区域内结束拖拽;在这种情况下,touch up action 不应该触发,但它仍然会触发。解决此问题的方法是在 touchesBegan、touchesMoved、touchesEnded 和 touchesCancelled 方法之外添加 hitTest:withEvent: 方法实现。 即请尝试在您的类中添加以下代码:- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event if ([_path containsPoint:point]) return self; else return nil;

以上是关于在 UIButton 上实现触摸取消的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式为处于触摸状态的 UIButton 创建灰色叠加层?

在移动设备上实现“更少”时仅触摸一次移动火力

如果移出 CGPoint 则取消触摸

Swift:在自定义视图中未调用 UIButton 触摸事件

在 UICollectionView 底部加载其他单元格

如何告诉 UIGestureRecognizer 取消现有的触摸?