深入浅出iOS事件机制
什么是响应链
响应链是一堆可以处理事件的对象构成的具有先后循序的链条。
iOS 中有一个类叫做 UIResponder
,它的头文件如下图:
UIResponder
是可以响应事件的的类的基类,只有继承自 UIResponder
的类才可以响应用户的操作。包括最常见的 UIView
、 UIWindow
和UIViewController
等。
在我们的app中,所有的视图都是按照一定的结构组织起来的,即树状层次结构,每一个View 都有自己的superView。当一个View被添加到superView上时,View的nextResponder
就被指向superView,UIViewController
的self.view 被添加到superView上时,self.view 的 nextResponder
指向UIViewController
,UIViewController
的 nextResponder
指向superView。这样整个app 就通过 nextResponder
形成了一条链,也就是我们说的响应链。
如何确认第一个响应者
响应链建立后,当用户点击屏幕时,如何确认第一个响应者?
每当手指接触屏幕,UIApplication接收到手指的事件之后,就会去调用UIWindow的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
,看看当前点击的点是不是在window内,如果点击的点不在UIWindow内,第一响应者就是UIApplication,如果点击的点在UIApplication内部,则继续依次调用window的subView的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
方法,查看点击的点是否在当前subView试图内,如果不在则第一响应者就是window,如果在就继续上面的流程,遍历子视图,直到找到最终的视图。最后找到的视图就是第一个响应者。
其实只是单纯的说点击的点在不在视图内是不准确的,因为- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
方法判断的不只是点范围的问题,具体的还包括视图是否隐藏、视图是否可交互、视图的透明度是否大于0.01等,这些都满足后,才能确认这个视图是否响应事件。下面这张图很清楚的画出了这个流程:
其实还有一种容易疑惑的情景,当两个视图有重叠时,如何确认那个视图响应事件。
当用户点击红色和蓝色重叠的部分时,系统是如何确认红色和蓝色那个视图响应事件呢?
这就牵扯到subview调用- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event
的顺序,调用的顺序是根据subview的index顺序,index越大越先被访问(越后被添加到父视图,index越大)。如果确认是蓝色视图响应事件,红色视图的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event
方法就不会再被调用。
响应链的工作机制
当确认了第一个响应者后,事件就被传递到这个响应者处理。
如果他不出理,就会通过nextResponder
传递到下一个响应者处理,知道找到一个响应者处理,或者到了AppDelegate仍未找到响应者处理,事件丢弃。
可以用来做什么
最常见的应用就是改变按钮的点击相应区域。
参考链接