写写代码,聊聊生活

0%

UIControl 事件细节

最近在使用 hitTest 方法自定义响应事件的 view 时发现了一个问题,如果是 UIControl 或者其子类响应事件,在一些事件上,会有一个70pt的边界值

以 UIButton 为例,亲测下面几个事件都受70pt边界值的影响

  • UIControlEventTouchDragInside

    看事件命名应该是在button里面拖拽时事件才会响应,但实际情况是,即使手指位置超出了button边界还是有响应的,直到远离button边界外70个pt后才会停止响应

  • UIControlEventTouchDragOutside

    看事件命名应该是拖拽button直到出了边界后才会有响应,但实际情况是,要拖拽到超出了button边界外70个pt后才会有响应

  • UIControlEventTouchDragExit

    看事件命名应该是拖拽button直到出了边界后响应,但实际情况是,要拖拽到button边界外70个pt才会有响应

  • UIControlEventTouchUpInside

    看事件命名应该是点击button,直到手指离开并且在button边界内才响应事件,但实际情况是,手指离开在button边界外70pt范围内都有响应

  • UIControlEventTouchUpOutside

    看事件命名应该是点击button,直到手指离开并且在button边界外就响应,但实际情况是,手指离开在button边界外70pt范围才有响应

实现点击蓝色按钮时,让红色按钮响应事件,按钮监听的事件是UIControlEventTouchUpInside

1

假设红色按钮和蓝色按钮都是相同的类,且是UIButton的子类,在该类中重写 hitTest 方法就能办到,其中 matrix 代表红色按钮

1
2
3
4
5
6
7
8
9
10
11
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL inside = [self pointInside:point withEvent:event];
if (!inside) {
return nil;
}
if (self.matrix) {
return [self.matrix hitTest:point withEvent:event];
}
return [super hitTest:point withEvent:event];
}

尝试点击蓝色按钮,只有在绿色线所画范围内点击事件才有响应

2

绿色竖线距离红色按钮的距离刚好是70pt

虽然给 hitTest 方法 传的 point 是在button范围内的,但是我猜测 UIControl 底层实现事件传递时,还会使用 UITouch 的 locationInView 方法去获取手指触摸的真实的点,再决定是否响应事件

如果手指触摸的点距离button边界超出70pt的话,就会把事件丢弃了

通过 hook UITouch 的 locationInView 方法尝试修改 point 的计算方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
其中 btn.source 表示蓝色按钮
- (CGPoint)my_locationInView:(UIView *)view
{
if (![view isKindOfClass:MyButton.class]) {
return [self my_locationInView:view];
}
MyButton *btn = (MyButton *)view;
if ([btn.name isEqualToString:@"left"] && btn.source) {
CGPoint p = [self my_locationInView:view];
if (p.x > btn.frame.size.width) {
p = [self my_locationInView:btn.source];
}
return p;
}
return [self my_locationInView:view];
}

hook 后,点击蓝色按钮都有红色按钮都能响应了,没有了边界内70pt的限制,印证了这个猜测

这个边界内70pt的机制,苹果可能是为了提升按钮点击的灵敏度、体验而设置的,但是某些场景下还是会带来一些麻烦,目前暂时没有想到比较好的解决方案