UITouchEvent事件分发机制
根据右边的图来简要说一下hitTest检测流程:
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;
}
附:
- (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;
}
}