UITouchEvent 笔记
- 如何确定点击事件由谁来响应
- 如何中断响应链
确定响应者
图释:
view 接收到 -hitTest 消息会通过自己调用 -pointInSize:withEvent: 来判断该点是不是在自己内部。下面以(Judge)来表示 view 通过该方法的判断结果。
触点1--A(Judge = YES)--B(Judge = NO)、D(Judge = NO)、E(Judge = NO)、F(Judge = NO)--A
触点2--A(Judge = YES)--B(Judge = YES)--C(Judge = YES)--C
触点3--A(Judge = YES)--B(Judge = NO)--E相对D在顶部,优先遍历E(Judge = YES)--E
触点4--不在A内--nil
归纳:
-hitTest: 会首先在 application 的 keyWindow 上调用(UIWindow 也是 UIView 的子类),并且该方法的返回值将被用来处理事件。会先判断产生触摸的 point 是否发生在自己的 bounds 内,如果没有将返回 nil;如果 point 在自己的范围内,则会为自己的每个子视图调用 -hitTest: 方法,只要有一个子视图通过这个方法返回一个 UIView 对象,那么整个方法就一层一层地往上返回;如果没有子视图返回 UIView 对象,则父视图将会把自己返回。
补充:
如果某个 view 的 enabled 或 userInteractionEnable 为 NO 或者 alpha<0.01 ,那么该view 的 -hitTest永远(-pointInSize:withEvent:返回YES/NO)都会返回nil,这意味着它和它的子视图没有机会去接收和处理事件。
在自定义控件时,我们经常要响应一个并非是矩形的区域,那么这时候,就可以通过重写 view 的-pointInSize:withEvent: 来限制响应区域了。
默认的写法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return CGRectContainsPoint(self.bounds, point);
}
现在想将范围限定在一个圆形内:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL inside = [self.superview pointInside:point withEvent:event];
if (inside) {
CGFloat radius = self.frame.size.width / 2;
CGFloat dx = point.x - radius;
CGFloat dy = point.y - radius;
CGFloat distace = sqrt(dx * dx + dy * dy);
return distace < radius;
}
return inside;
}
附(-hitTest:withEvent:的等效实现):
- (UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden) {
return nil;
}
BOOL inside = [self pointInside:point withEvent:event];
UIView *hitView = nil;
if (inside) {
NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
for (UIView *subview in enumerator) {
if (hitView) {
break;
}
}
if (!hitView) {
hitView = self;
}
return hitView;
}
else {
return nil;
}
}
响应者链
寻找响应者是由下往上找,响应者处理事件则是由上往下处理。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
如果想要中断响应者链,只需在这些代理事件中不要调用对应的 super 就可以了。