iOS事件响应链及hit-test原理总结
响应者对象(UIResponder)
在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接受并处理事件,我们称之为“响应者对象”。以下都是继承自UIResponder的,所以都能接收并处理事件。
- UIApplication
- UIViewController
- UIView
那么为什么继承自UIResponder的类就能够接收并处理事件呢?
因为UIResponder中提供了以下4个对象方法来处理触摸事件。
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;
加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
iOS 事件响应链全过程分两步:
一.事件的传递:从用户触摸位置找到最上层view,这个过程是从底层向上层寻找的过程。
iOS使用hit-testing寻找触摸的view。
Hit-Testing 叫做命中测试。
Hit-Testing通过检查触摸点是否在关联的view边界内,如果在,则递归地检查该view的所有子view。
在层级上处于lowest(就是离用户最近的view)且边界范围包含触摸点的view成为hit-test view。确定hit-test view后,它传递触摸事件给该view。
下面是官方的小例子:
hit-test过程- 触摸点在view A中,所以要先检查子view B和C。
- 触摸点不在view B中,但在C中,所以检查C的子view D和E。
- 触摸点不在D中,但在E中。View E是这个层级上处于lowest的- view的边界范围包含触摸点,所以它成为了hit-test view。
Hit-test view是处理触摸事件的第一选择,如果hit-test view不能处理事件,该事件将从事件响应链中寻找响应器,直到系统找到一个处理事件的对象。若不能处理,则就有事件传递链了,继续看下面的事件传递链。
Hit-Test命中测试方法判断一个view能否作为Responder还有3个条件:
- 透明度<0.01
- hidden=YES的时候
- userInteractionEnabled = NO
以上这3个情况,该view(以及该view所有的子视图) 命中测试方法会忽略掉,也就不会有事件响应了。
二.事件响应:从上层沿着响应链传到底层,和第一步刚好是相反的。
响应者对象:能处理事件的对象,也就是继承自UIResponder的对象
作用:能很清楚的看见每个响应者之间的联系,并且可以让一个事件多个对象处理。
如何判断上一个响应者
1> 如果当前这个view是控制器的view,那么控制器就是上一个响应者
2> 如果当前这个view不是控制器的view,那么父控件就是上一个响应者
下面是参考官方的响应流程图:
事件传递过程左半图:
initial view若不能处理事件,则传到其父视图view
view若不能处理,则传到其父视图,因为它还不是最上层视图
这里view的父视图是view controller的view,因为这个view也不能处理事件,因此传给view controller
若view controller也不能处理此事件,则传到window
若window也不能处理此事件,则传到app单例对象Application
若UIApplication单例对象也不能处理,则表示无效事件
右半图:
initial view一直传递直到最上层view(原话:A view passes an event up its view controller’s view hierarchy until it reaches the topmost view.)
topmost view传递事件到它所在的控制器(原话:The topmost view passes the event to its view controller.)
view controller传递事件到topmost view的父视图,重复前三步,走到到达root controller(原话:passes the event to its topmost view’s superview. Steps 1-3 repeat until the event reaches the root view controller.)
由root控制器传递事件到window(原话:The root view controller passes the event to the window object.)
若window也不能处理此事件,则传到app单例对象Application
若UIApplication单例对象也不能处理,则表示无效事件
事件处理的整个流程总结:
- 触摸屏幕产生触摸事件后,触摸事件会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。
- UIApplication会从事件队列中取出最前面的事件,把事件传递给应用程序的主窗口(keyWindow)。
- 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。(至此,第一步已完成)
- 最合适的view会调用自己的touches方法处理事件
- touches默认做法是把事件顺着响应者链条向上抛。
如何做到一个事件多个对象处理:
因为系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到一个事件多个对象处理的目的。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 1.自己先处理事件...
NSLog(@"do somthing...");
// 2.再调用系统的默认做法,再把事件交给上一个响应者处理
[super touchesBegan:touches withEvent:event];
}
事件的传递和响应的区别:
事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。