概述
用户触发某一事件时,生成一个UIEvent对象,事件对象会加入一个FIFO队列中,UIApplication处理事件时,会先从队列中取出头部的事件对象进行分发。首先找到Keywindow,keywindow首先会把事件交给Gestrue,如果gestrue识别了传递过来的事件,那就调用相应的target处理,gestrue识别了事件后就不会向下传递了,如果没有识别,那就把事件传递给视图图形树,然后分成寻找事件的第一响应者和寻找事件的最终响应者两个步骤。在视图图形树中寻找事件的第一响应者即触摸事件发生的最上层那个view的过程叫做hit-test。在视图图形树中寻找事件的最终响应者的过程叫做responder chain。
寻找第一响应者
第一响应者指的是当前接受触摸的响应者对象,它是响应者链的开端。
iOS 系统检测到手指触摸 (Touch) 操作时会将其打包成一个 UIEvent 对象,并放入当前 Application 的事件队列, UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,UIWindow 对象首先会使用 hitTest方法寻找此次 Touch 操作触摸点所在的视图(View),这个过程称之为 hit-test。
hitTest方法的处理流程如下:
- 首先调用当前视图的 pointInside方法,判断触摸点是否在当前视图内;
- 若返回 NO, 则 hitTest返回 nil,若返回 YES, 则向当前视图的所有子视图 (subviews) 发送 hitTest消息,所有子视图的遍历顺序是从 subviews 数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
- 若第一次有子视图返回非空对象,则 hitTest方法返回此对象,处理结束;
- 若所有子视图都返回空,则 hitTest方法返回自身 (self)。 hitTest示例代码:
(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
寻找最终响应者
对于触摸事件来说,无论View是否处理事件,即使是application通过[application beginIgnoringInteractionEvents]忽略了触摸事件,上面hit-test的过程依然存在,它只影响第二个步骤事件响应的过程
事件首先被发送给第一响应者,由第一响应者(对于触摸事件即hit-test view)开始向上传递,分两种情况:
1,如果该视图是视图控制器的根视图:根视图--视图控制器--父视图--UIWindow--UIApplication。
2,如果该视图不是视图控制器的根视图:子视图--父视图---根视图--视图控制器-UIWindow--UIApplication
hit-test是从下往上寻找事件的响应者 ,responder chain事件响应是从上往下寻找事件的最终响应者;