事件分发机制
当屏幕上有事件发生时,事件会自上而下,去检查,谁来响应。
- 前面几个基佬是系统层级的响应,顺序是
UIApplication - > UIWindow - > ...
这种响应规则就是传说中的hitTest,通过这个名字就看出来个大概,实现则是:
- (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) {
hitView = [subview hitTest:point withEvent:event];
if (hitView) {
break;
}
}
if (!hitView) {
hitView = self;
}
return hitView;
}
else {
return nil;
}
}
据说内部是这样实现的,看到它大概就是先判断当前的状态是否适合有事件产生。然后就是通过pointinside来判断点击的点是否在自己的frame内。如果满足就去遍历他里面的所有subView,如果没有合适的subview来响应,就只有自己来了。这大概就是事件产生时寻找它响应的view的过程。
注意几点:
1、默认的hit-testing顺序是按照UIView中Subviews的逆顺序
2、如果View的同级别Subview中有重叠的部分,则优先检查顶部的Subview,如果顶部的Subview返回nil, 再检查底部的Subview
3、Hit-Test也是比较聪明的,检测过程中有这么一点,就是说如果点击没有发生在某View中,那么该事件就不可能发生在View的Subview中,所以检测过程中发现该事件不在ViewB内,也直接就不会检测在不在ViewF内。也就是说,如果你的Subview设置了clipsToBounds=NO,实际显示区域可能超出了superView的frame,你点击超出的部分,是不会处理你的事件的,就是这么任性!
响应者链Responder Chain
找到这个view后,就会涉及到会找到这个view上的UIResponder,这个东西进去看就知道是一堆事件的集合。
然后它会根据传递链,来自下而上的寻找谁能处理这个事件。谁能处理,就在这结束,不往上寻找。
响应链条的响应顺序是view->superview(or vc)->superview(or vc)->window->UIApplication,ps如果中间遇到vc的,就绕下vc,在向上。到头没了就废弃,不处理。
总结,ios的触摸事件是2个顺序:
- 是自上而下以hitTest的机制寻找响应的view.
- 是找到之后又自下而上的寻找响应事件。