hit-test view
当用户手指在屏幕上触摸时,系统会检测到用户触摸并将触摸信息包装成事件放入应用的Run loop
中等待被处理。待UIApplication
单例处理完手头工作后,将事件从Run loop
中取出,并发送给UIWindow
单例。UIWindows
单例通过递归调用hittest:withEvent:
方法找到触摸所在视图,此时这个视图就称作hit-test view
。找到hit-test view
后,UIWindow
把事件交给其处理。一般来说,这个视图此时就是first responder
(第一响应者)。
在如下图所示的视图层次中,假设用户触摸的时E视图,在调用hittest:withEvent:
时
- 首先对A进行测试,返回
YES
,继续对B
和C
进行测试 - 对
B
测试返回NO
,不再继续往下测试,转而对C
进行测试 - 对
C
测试返回YES
,继续对D
和E
进行测试 - 对
D
测试返回NO
,不再继续往下测试,转而对E
进行测试 - 对
E
测试返回YES
,由于E
是最低一级视图,所以hittest:withEvent:
返回视图E
在调用hittest:withEvent:
时会传入两个参数:一个GCPoint
和一个UIEvent
,GCPoint
表示手指触摸屏幕时的坐标,UIEvent
包装了此次触摸的触摸事件。hittest:withEvent:
执行时会首先对自己调用pointInside:withEvent:
来测试GCPoint
是否在自己的bounds
范围内,如果在,返回YES
并继续对自己的子视图递归调用hittest
,不在返回NO
并且hittest:withEvent:
返回nil
。
一旦
hittest:withEvent:
返回nil
的话就不会再继续对其子视图调用hittest:withEvent
通过hittest:withEvent:
找到的hit-test view
将有第一次机会去处理触摸事件,如果不能够处理的话,将会把事件沿着响应者链往下传递来找到能够处理事件的对象。
如果在
UIView
上附加了UIGestureRecognozer
可能会使UIWindow
延迟或取消向UIView
发送事件而由UIGestureRecognozer
进行接收识别并交由UIGestureRecognozer
的target
进行处理
响应者链
first responder
响应者链中第一个有机会处理事件的对象叫做first responder
(第一响应者)。
first responder
通常是一个UIView
对象。
触摸事件中,
frist responder
通常是hit-test view
如果要让一个对象成为第一响应者,要做到以下两件事情:
- 重写
canBecomeFirstResponder
方法并返回YES
- 调用
becomeFirstResponder
方法
应该在视图出现之后调用
becomeFirstResponder
方法,比如说在viewDidAppear
方法中调用becomeFirstResponder
方法。如果在视图还未出现之前调用becomeFirstResponder
方法的话将会失败,比如说在viewWillAppear
中调用becomeFirstResponder
方法将会返回NO
事件传递
响应者链由一系列UIResponder
的子类组成,并且其末尾为UIApplication
单例。如果响应者链中的某个对象不能处理事件,事件将沿着响应者链向上进行传播。如果链上的所有对象都不能处理事件,事件最终会传播到UIApplication
单例对象,如果UIApplication
单例对象也不能进行处理,那么事件最终会被丢弃。
UIResponder
定义了以下四个方法:
- (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
并且一般来说
- 如果一个
UIView
有它自己的UIViewController
的话那么它的nextResponder
就是它的UIViewController
,否则就是它的父视图 UIViewController
的nextResponder
是它的view
的父视图- 根视图或根试图控制器的
nextResponder
是UIWindow
UIWindow
的nextResponder
的UIApplication
因为响应者链中的所有的对象都是UIResponder
的子类,因此,当某个事件无法处理并且要交给下一个响应对象处理的时候应该调用
[super touchesXXX:touches withEvent:event];
而不是
[self.nextResponder touchesXXX:touches withEvent:event];
因为调用后者可能会漏掉父类对事件的一些其他的处理。
大致的事件传递流程可以参考下图