解决UIButton拖动响应事件距离问题

1,点击事件和touch事件的关系

自定义UIButton并在其中重写以下方法:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    BOOL isInside = [super pointInside:point withEvent:event];
    NSLog(@"Button is inside: %zd", isInside);
    return isInside;
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *view = [super hitTest:point withEvent:event];
    NSLog(@"Button hit: %@", view);
    return view;
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches began");
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches moved");
    [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches ended");
    [super touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches cancelled");
    [super touchesCancelled:touches withEvent:event];
}

添加UIButton并监听UIControlEventTouchDown和UIControlEventTouchUpInside事件:

- (void)viewDidLoad {
    [super viewDidLoad];
    JKRButton *button = [JKRButton buttonWithType:UIButtonTypeCustom];
    [button setBackgroundColor:[UIColor blueColor]];
    [button setTitle:@"normal" forState:UIControlStateNormal];
    [button setTitle:@"highlighted" forState:UIControlStateHighlighted];
    button.frame = CGRectMake(100, 100, 100, 40);

    [button addTarget:self action:@selector(touchDownAction) forControlEvents:UIControlEventTouchDown];
    [button addTarget:self action:@selector(touchUpInsideAction) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:button];
}

- (void)touchDownAction {
    NSLog(@"Action touch down");
}

- (void)touchUpInsideAction {
    NSLog(@"Action touch up inside");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"rootview touchBegan");
}

点击按钮查看输出:

Button is inside: 1
Button hit: <JKRButton: 0x7fcef9508ae0; baseClass = UIButton; frame = (100 100; 100 40); opaque = NO; layer = <CALayer: 0x608000023d80>>
Button touches began
Action touch down
Button touches ended
Action touch up inside

点击按钮后,首先通过输出可以看到首先通过响应者遍历找到UIButton,触发Button的touches began方法,Button的TouchDown事件触发并调用touchDownAction方法。
松开按钮后,首先出发UIButton的touches ended方法,Button的TouchUpInside事件触发调用touchUpInsideAction方法。
Button按钮的点击事件阻断它的父视图的touch方法,所以控制器的touches began方法并没有调用。

现在测试UIButton的点击和touch事件的关系:

测试一:重写Button的pointInside方法返回NO,使Button不能响应touch事件:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    BOOL isInside = [super pointInside:point withEvent:event];
    isInside = false;
    NSLog(@"Button is inside: %zd", isInside);
    return isInside;
}

点击按钮查看输出:

Button is inside: 0
Button hit: (null)
rootview touchBegan

当UIButton不能称为touch事件响应者时,UIButton不能够被点击,并且父视图响应到touch事件。

结论:UIButton的点击事件是通过touch事件来响应的,并且它并没有向上将事件传递给上一级响应者。

测试二:注释掉UIButton的touches began的super方法

点击按钮查看输出:

Button is inside: 1
Button hit: <JKRButton: 0x7fb81b408fe0; baseClass = UIButton; frame = (100 100; 100 40); opaque = NO; layer = <CALayer: 0x60800003f840>>
Button touches began
Button touches ended

这里看到,按钮不能够被点击

测试三:注释掉UIButton的touches ended的super方法点击按钮查看输出:

记得去掉touches began的注释打开super方法,输出如下:

Button is inside: 1
Button hit: <JKRButton: 0x7fed2950e140; baseClass = UIButton; frame = (100 100; 100 40); opaque = NO; layer = <CALayer: 0x60000002be60>>
Button touches began
Action touch down
Button touches ended

这里看到,按钮被点击,但是松开按钮后,按钮不能够从高亮状态恢复:

按钮高亮状态无法恢复
结论:UIButton的touch down事件和高亮状态的转换取决于touches began方法的处理,touch up inside事件是否触发取决于touch down事件是否触发。
结论:UIButton的touch up inside事件和从高亮状态恢复到normal状态取决于touches ended方法的处理。(如果高亮状态下,没有走touches ended方法,直接走了touchesCancelled方法,touchesCancelled也会做高亮状态恢复的处理,后面UIButton和手势那里有测试)

2,UIButton的使用

传递UIButton的点击事件给上一级响应者。

上面看到,UIButton会阻断父视图的响应链,这里尝试测试以下代码:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches began");
    [super touchesBegan:touches withEvent:event];
    [self.nextResponder touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches moved");
    [super touchesMoved:touches withEvent:event];
    [self.nextResponder touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches ended");
    [super touchesEnded:touches withEvent:event];
    [self.nextResponder touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches cancelled");
    [super touchesCancelled:touches withEvent:event];
    [self.nextResponder touchesCancelled:touches withEvent:event];
}

重新点击按钮发现,touches began、touches ended方法可以传递给它的父视图,但是touches moved方法只能传递一次,这里原因还不清楚。

触发UIButton的点击方法

上面看到,UIButton通过接收到ControlEvent事件来触发点击方法,这里通过给UIButton发送一个事件来触发UIButton的点击方法:

//触发touchDown事件:
[self.button sendActionsForControlEvents:UIControlEventTouchDown];
//触发touchUpInside事件:
[self.button sendActionsForControlEvents:UIControlEventTouchUpInside];

3,UIButton和手势

给UIButton添加一个Tap手势:

JKRTapGestureRecognizer *tap = [[JKRTapGestureRecognizer alloc] initWithTarget:self action:@selector(otherAction)];
[button addGestureRecognizer:tap];

- (void)otherAction {
    NSLog(@"Tap action");
}

点击按钮,查看log:

Button is inside: 1
Button hit: <JKRButton: 0x7ff598d0bbc0; baseClass = UIButton; frame = (100 100; 100 40); opaque = NO; gestureRecognizers = <NSArray: 0x600000241470>; layer = <CALayer: 0x608000029000>>
Tap touchBegan
Button touches began
Action touch down
Tap touchEnded
Tap RecognizerShouldBegin
Tap action
Button touches cancelled

这里的输出顺序和UIView添加手势的顺序一样,手势的touch方法先于UIButton的touch一样。这里注意的就是,UIButton的touches began方法调用后,会马上出发UIButton的touch down,所有按钮的touch down事件优先于手势事件处理。在touch ended方法的方法调用中,依然和UIView添加手势的顺序一样,手势的touch ended方法优先执行。这时,识别到手势,触发Tap action方法,然后取消UIButton的touch事件,所以UIButton调用touches cancelled方法。上面说到,按钮的高亮在touches began调用,touches ended恢复,这里由于没有走touches ended。所以可以知道,touches cancelled在没有调用touches ended的情况下,完成了按钮高亮的恢复。

测试一:修改tap手势的delaysTouchesBegan为YES

点击按钮输出:

Button is inside: 1
Button hit: <JKRButton: 0x7fdd9360aab0; baseClass = UIButton; frame = (100 100; 100 40); opaque = NO; gestureRecognizers = <NSArray: 0x608000058420>; layer = <CALayer: 0x608000029600>>
Tap touchesBegan
Tap touchesEnded
Tap RecognizerShouldBegin
Tap action

手势成果的阻断了按钮的点击事件。

点击按钮并滑动输出:

Button is inside: 1
Button hit: <JKRButton: 0x7fdd9360aab0; baseClass = UIButton; frame = (100 100; 100 40); opaque = NO; gestureRecognizers = <NSArray: 0x608000058420>; layer = <CALayer: 0x608000029600>>
Tap touchesBegan
Tap touchesMoved
Tap touchesMoved
Tap touchesMoved
Button touches began
Action touch down
Button touches moved
Button touches moved
Button touches ended
Action touch up inside

手势没有识别,UIButton在tap手势没有识别后,延时执行touch事件,并调用了按钮点击的方法。

测试二:注释掉UIButton的touches cancelled方法中的super调用

Button is inside: 1
Button hit: <JKRButton: 0x7ffa48d0f3d0; baseClass = UIButton; frame = (100 100; 100 40); opaque = NO; gestureRecognizers = <NSArray: 0x6080002408a0>; layer = <CALayer: 0x608000036cc0>>
Tap touchesBegan
Button touches began
Action touch down
Tap touchesEnded
Tap RecognizerShouldBegin
Tap action
Button touches cancelled

点击按钮后发现,按钮停留在高亮状态无法恢复,验证了之前的想法:按钮的高亮在touches began调用,touches ended恢复,这里由于没有走touches ended。所以可以知道,touches cancelled在没有调用touches ended的情况下,完成了按钮高亮的恢复。

4,UIButton的事件的详细解析

UIControlEventTouchDown:按钮点下就调用
UIControlEventTouchUpInside:在按钮范围内松开手指调用
UIControlEventTouchUpOutside:在按钮范围外松开手指调用
UIControlEventTouchCancel:按钮touch事件被取消调用
UIControlEventTouchDragInside:点击按钮后,在按钮范围内拖动反复调用
UIControlEventTouchDragOutside:点击按钮后,在按钮范围外拖动反复调用
UIControlEventTouchDragEnter:点按钮后,拖动到按钮范围外又拖动回按钮返回内跨越边界时调用一次
UIControlEventTouchDragExit:点击按钮,从按钮范围内拖动到按钮范围外跨越边界时调用一次

上面所说的按钮范围比实际按钮的尺寸要大,大约是按钮的尺寸加上一70px的边距。

5,深入理解按钮的事件

深入理解按钮事件,必须先了解UIButton的继承结构,UIButton继承自UIControl,UIButton的事件(UIControlEvent)和触发(addTarget)也是基于UIControl。UIControl的事件监听和发送基于以下几个方法:

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)event; 
- (void)cancelTrackingWithEvent:(nullable UIEvent *)event;   

beginTrackingWithTouch:该方法并不是在按钮中开始拖动时调用,而是touchesBegan后马上调用。这个方法的返回值决定了是否要追踪点击事件并进行event事件的处理,如果返回NO,那么UIControl的事件都不会被处理。

continueTrackingWithTouch:该方法是在touchesMoved调用后调用,决定了按钮拖动后的事件追踪并进行event事件的处理,如果返回为NO,那么拖动之后的所有事件都不会处理,包括除UIControlEventTouchDown之外的所有事件的处理和高亮状态的恢复都不会进行。它会调用的事件:UIControlEventTouchDragInside、UIControlEventTouchDragOutside、UIControlEventTouchDragEnter、UIControlEventTouchDragExit。

endTrackingWithTouch:该方法在touchesEnded调用后调用,决定了按钮松开后的事件追踪和event事件的处理。它和UIControlEventTouchUpInside、UIControlEventTouchUpOutside相关。

cancelTrackingWithEvent:该方法在touchesCanceled调用后调用,当按钮的touch事件被取消或者手动调用该方法后调用。该方法会取消UIControl事件的监听,并让按钮从高亮状态恢复到正常状态,并发送UIControlEventTouchCancel事件。如果按钮的touch事件是被主动取消的(例如被其它手势对象识别并取消touch事件),该方法会调用但是不会发送UIControlEventTouchCancel事件。

按钮中的track相关方法是连续的,如果中途有中断,那么按钮之后的所有点击处理都不能继续执行,例如在点击按钮后拖动过程continueTrackingWithTouch事件中返回NO,那么按钮之后的所有事件和UI处理都不会继续进行,UIControlEventTouchUpInside、UIControlEventTouchUpOutside、UIControlEventTouchDragInside、UIControlEventTouchDragOutside、UIControlEventTouchDragEnter、
UIControlEventTouchDragExit事件以及高亮状态的恢复都不会执行。重写该方法要记得调用super方法,按钮touch事件取消时的高亮状态恢复是在这里执行的。

按钮的UI状态、事件的处理,是通过touch事件和UIControl的track相关方法共同完成的,测试中发现并不能对它的事件发送做过多的干涉,否则会造成UI状态和事件处理的中断,所以需要反复调试找到合理的方案。

6,重定义按钮事件:创建一个手指拖动到按钮外就取消touch响应的按钮

1,touch事件简单处理

首先这个操作需要在TouchUpInside事件去处理,点击后松开手指如果手指在按钮范围外就不执行这个事件。但是默认按钮的实际滑动范围的比按钮的尺寸大70的边距。所以这里做的就是重写按钮的touchesMoved方法,监听到拖动的点出了按钮的范围,就直接touchCancelled:

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"Button touches moved");
    UITouch *touch = touches.anyObject;
    CGPoint touchPoint = [touch locationInView:self];
    NSLog(@"%@ -- %@", NSStringFromCGRect(self.bounds), NSStringFromCGPoint(touchPoint));
    BOOL cancel = !CGRectContainsPoint(self.bounds, touchPoint);
    if (cancel) {
        [self touchesCancelled:touches withEvent:nil];
    } else {
        [super touchesMoved:touches withEvent:event];
    }
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
}

现在,点击按钮后,按钮执行UIControlEventTouchDown事件,按钮内拖动,按钮执行UIControlEventTouchDragInside事件,按钮拖动到按钮尺寸范围后,直接调用按钮的touchCancelled方法,并执行按钮的UIControlEventTouchCancel事件。
只有在按钮点击后,中途没有拖动到按钮外,并在按钮范围内松开,按钮才会响应UIControlEventTouchUpInside事件。
这样修改后,按钮的UIControlEventTouchUpOutside事件不会触发了,因为滑出按钮范围,直接就走了UIControlEventTouchCancel事件。
所以该修改只能让按钮点击拖出范围后马上取消事件处理并恢复高亮状态到默认状态,并不能实现重新拖回按钮内再次响应。

2,UIControl层次的轨迹监听处理

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"continueTrackingWithTouch");

    BOOL isCurInRect = CGRectContainsPoint(self.bounds, [touch locationInView:self]);
    BOOL isPreInRect = CGRectContainsPoint(self.bounds, [touch previousLocationInView:self]);
    
    if (!isCurInRect) { // 现在在外面
        if (isPreInRect) { // 之前在里边
            // 从里边滑动到外边
            [self sendActionsForControlEvents:UIControlEventTouchDragExit];
            [self touchesCancelled:[NSSet setWithObject:touch] withEvent:event];
            return NO;
        } else {  // 之前在外边
            // 在按钮外拖动
            [self touchesCancelled:[NSSet setWithObject:touch] withEvent:event];
            return NO;
        }
    } else { // 现在在里边
        if (!isPreInRect) { // 之前在外边
            // 从外边滑动到里边
            [self sendActionsForControlEvents:UIControlEventTouchDragEnter];
            return [super continueTrackingWithTouch:touch withEvent:event];
        } else { // 之前在里边
            // 在按钮内拖动
            return [super continueTrackingWithTouch:touch withEvent:event];
        }
    }
    
    return [super continueTrackingWithTouch:touch withEvent:event];
}

该方案的效果和上面一样,但是面临几个问题:
1,该方法返回NO后,UIControl的事件监听也不会进行了,按钮外的滑动不会触发UIControlEventTouchDragOutside事件。
2,原因同上,按钮从外部滑动会内部,也不会重新恢复高亮状态,UIControlEventTouchDragEnter、UIControlEventTouchDragInside、UIControlEventTouchUpInside事件也不会重新处理。

3,优化处理第一步,按钮外滑动重新出发UIControlEventTouchDragOutside事件

既然UIControl的事件无法处理,continueTrackingWithTouch不会再调用,那么我们尝试在touchesMoved方法中手动调用:

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesMoved");
    UITouch *touch = touches.anyObject;
    BOOL isCurInRect = CGRectContainsPoint(self.bounds, [touch locationInView:self]);
    BOOL isPreInRect = CGRectContainsPoint(self.bounds, [touch previousLocationInView:self]);
    if (!isCurInRect) { // 现在在外面
        if (isPreInRect) { // 之前在里边
            // 从里边滑动到外边
            
        } else {  // 之前在外边
            // 在按钮外拖动
            [self sendActionsForControlEvents:UIControlEventTouchDragOutside];
        }
    } else { // 现在在里边
        if (!isPreInRect) { // 之前在外边
            // 从外边滑动到里边
        
        } else { // 之前在里边
            // 在按钮内拖动
        }
    }
    [super touchesMoved:touches withEvent:event];
    
}

下面发现,按钮在外部滑动,也会触发UIControlEventTouchDragOutside事件了。

4,优化处理第二部,滑动会按钮重新进入点击状态并触发相应事件。

上面分析得出,UIControl的事件监听被截断了,而它的开始是从beginTrackingWithTouch方法开始的,尝试在touchesMoved方法中当滑动回按钮范围内的时刻,重新开始UIControl事件的监听,即手动调用beginTrackingWithTouch方法:

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesMoved");
    UITouch *touch = touches.anyObject;
    BOOL isCurInRect = CGRectContainsPoint(self.bounds, [touch locationInView:self]);
    BOOL isPreInRect = CGRectContainsPoint(self.bounds, [touch previousLocationInView:self]);
    if (!isCurInRect) { // 现在在外面
        if (isPreInRect) { // 之前在里边
            // 从里边滑动到外边
            
        } else {  // 之前在外边
            // 在按钮外拖动
            [self sendActionsForControlEvents:UIControlEventTouchDragOutside];
        }
    } else { // 现在在里边
        if (!isPreInRect) { // 之前在外边
            // 从外边滑动到里边
            [self beginTrackingWithTouch:touch withEvent:event];
        } else { // 之前在里边
            // 在按钮内拖动
        }
    }
    [super touchesMoved:touches withEvent:event];
}

运行测试发现并没有起效果,而之前已经分析出beginTrackingWithTouch方法是在touchesBegan方法之后调用的,可能是缺少了touchesBegan方法中的相应处理,尝试直接调用touchesBegan:

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesMoved");
    UITouch *touch = touches.anyObject;
    BOOL isCurInRect = CGRectContainsPoint(self.bounds, [touch locationInView:self]);
    BOOL isPreInRect = CGRectContainsPoint(self.bounds, [touch previousLocationInView:self]);
    if (!isCurInRect) { // 现在在外面
        if (isPreInRect) { // 之前在里边
            // 从里边滑动到外边
            
        } else {  // 之前在外边
            // 在按钮外拖动
            [self sendActionsForControlEvents:UIControlEventTouchDragOutside];
        }
    } else { // 现在在里边
        if (!isPreInRect) { // 之前在外边
            // 从外边滑动到里边
            //[self beginTrackingWithTouch:touch withEvent:event];
            [self touchesBegan:[NSSet setWithObject:touch] withEvent:event];
        } else { // 之前在里边
            // 在按钮内拖动
        }
    }
    [super touchesMoved:touches withEvent:event];
}

运行发现,按钮重新滑动回来,响应的事件可以正常触发,并且按钮可以重新恢复成高亮状态!

5,最后的优化,按钮范围外松开手指的事件触发

现在按钮已经满足除了按钮范围外松开手指的事件UIControlEventTouchUpOutside的其它所有事件的完美触发。
之所以不会触发这个事件,是因为上面我们其实在滑动出按钮范围后,就已经截断了UIControl的事件处理,UIControlEventTouchDragOutside的事件是我们在touchesMoved方法中手动触发的。
现在我们也在touch方法中,手动触发这个事件。因为我们之前已经分析出:UIControlEventTouchUpInside、UIControlEventTouchUpOutside都是在touchesEnded方法后触发的,所重写这个方法,当松开手指后的点在按钮范围外,就手动发送这个事件:

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesEnded");
    UITouch *touch = touches.anyObject;
    CGPoint point = [touch locationInView:self];
    if (!CGRectContainsPoint(self.bounds, point)) {
        [self sendActionsForControlEvents:UIControlEventTouchUpOutside];
    }
    [super touchesEnded:touches withEvent:event];
}
完整代码如下:
/// 修改按钮滑动范围
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesMoved");
    UITouch *touch = touches.anyObject;
    BOOL isCurInRect = CGRectContainsPoint(self.bounds, [touch locationInView:self]);
    BOOL isPreInRect = CGRectContainsPoint(self.bounds, [touch previousLocationInView:self]);
    if (!isCurInRect) { // 现在在外面
        if (isPreInRect) { // 之前在里边
            // 从里边滑动到外边
            
        } else {  // 之前在外边
            // 在按钮外拖动
            // 在按钮范围外拖动手动发送UIControlEventTouchDragOutside事件
            [self sendActionsForControlEvents:UIControlEventTouchDragOutside];
        }
    } else { // 现在在里边
        if (!isPreInRect) { // 之前在外边
            // 从外边滑动到里边
            // 从按钮范围外滑动回按钮范围内,需要手动调用touchesBegan方法,让按钮进入高亮状态,并开启UIControl的事件监听
            //[self beginTrackingWithTouch:touch withEvent:event];
            [self touchesBegan:[NSSet setWithObject:touch] withEvent:event];
        } else { // 之前在里边
            // 在按钮内拖动
        }
    }
    [super touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"touchesEnded");
    UITouch *touch = touches.anyObject;
    CGPoint point = [touch locationInView:self];
    // 如果松开手指后在按钮范围之外
    if (!CGRectContainsPoint(self.bounds, point)) {
        // 手动触发UIControlEventTouchUpOutside事件
        [self sendActionsForControlEvents:UIControlEventTouchUpOutside];
    }
    [super touchesEnded:touches withEvent:event];
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"continueTrackingWithTouch");
    BOOL isCurInRect = CGRectContainsPoint(self.bounds, [touch locationInView:self]);
    BOOL isPreInRect = CGRectContainsPoint(self.bounds, [touch previousLocationInView:self]);
    if (!isCurInRect) { // 现在在外面
        if (isPreInRect) { // 之前在里边
            // 从里边滑动到外边
            // 从按钮范围内滑动到按钮范围外手动触发UIControlEventTouchDragExit事件并阻断按钮默认事件的执行
            [self sendActionsForControlEvents:UIControlEventTouchDragExit];
            // 阻断按钮默认事件的事件的执行后,需要手动触发touchesCancelled方法,让按钮从高亮状态变成默认状态
            [self touchesCancelled:[NSSet setWithObject:touch] withEvent:event];
            return NO;
        } else {  // 之前在外边
            // 在按钮外拖动
            // 在按钮范围外滑动时,需要手动触发touchesCancelled方法,让按钮从高亮状态变成默认状态,并阻断按钮默认事件的执行
            [self touchesCancelled:[NSSet setWithObject:touch] withEvent:event];
            return NO;
        }
    } else { // 现在在里边
        if (!isPreInRect) { // 之前在外边
            // 从外边滑动到里边
            // 从按钮范围外滑动到按钮范围内,需要手动触发UIControlEventTouchDragEnter事件
            [self sendActionsForControlEvents:UIControlEventTouchDragEnter];
            return [super continueTrackingWithTouch:touch withEvent:event];
        } else { // 之前在里边
            // 在按钮内拖动
            return [super continueTrackingWithTouch:touch withEvent:event];
        }
    }
    return [super continueTrackingWithTouch:touch withEvent:event];
}

运行效果:

buttonEvent.gif

6,仍然存在的问题

最后唯一存在的问题就是从按钮范围内拖动出按钮范围外的时候,因为手动调用了touchesCancelled方法,导致按钮多余的发送了一次UIControlEventTouchCancel事件。

Demo:https://github.com/Joker-388/JKRButtonWithDragCancel

获取授权

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容