iOS视图响应者链以及响应处理和传递过程

来源:互联网 发布:诺基亚java单机游戏 编辑:程序博客网 时间:2024/06/04 18:45

用户点击屏幕后产生的一个触摸事件,经过一些列的传递过程后,会找到最合适的视图控件来处理这个事件,找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理,事件包括(不做详解):

touchesBegan…
touchesMoved…
touchedEnded…


一、响应者链的概念:

 iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。

响应者对象(Responder Object): 指的是 有响应和处理事件能力的对象。 响应者链就是由一系列的响应者对象 构成的一个层次结构


二、一次完整的触摸事件的传递响应的基本过程

 UIAppliction --> UIWiondw -->递归找到最适合处理事件的控件-->控件调用touches方法-->判断是否实现touches方法-->没有实现默认会将事件传递给上一个响应者-->找到上一个响应者

UIResponder 是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的 UIApplication、 UIViewController、 UIWindow 和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。


三、hitTest:withEvent:方法的处理流程如下:

首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内; 
若返回NO,则hitTest:withEvent:返回nil; 
若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕; 
若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束; 
如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。


四、我们通过一个实例来说明一下响应者链的查找和响应过程:

查找:

假如跟视图为A,在A上面添加了两个视图View B和C(这两个视图完全在A范围之内且不相交),在C的上面添加View D和E(这两个视图完全在C范围之内且不相交),然后用户点击了View E

hit-test view的流程:
1、A是UIWindow的根视图,因此,UIWindow对象会首相对A进行hit-test;
2、显然用户点击的范围是在A的范围内,因此, pointInside:withEvent:返回了YES,这时会继续检查A的子视图,调用A子视图上的hit-test;
3、这时候会有两个分支,B和C,点击的范围不再B内,因此B分支的 pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;点击的范围在C内,即C的 pointInside:withEvent:返回YES;这时会调用C子视图上的hit-tes;
4、这时候有D和E两个分支:点击的范围不再D内,因此D 的 pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;点击的范围在E内,即E的 pointInside:withEvent:返回YES,由于E没有子视图(也可以理解成对E的子视图进行hit-test时返回了nil),因此,E的 hitTest:withEvent:会将E返回,再往回回溯,就是C的 hitTest:withEvent:返回E,直到A的hitTest:withEvent:返回E。 至此,本次点击事件的第一响应者就通过响应者链的事件分发逻辑成功的找到了。

不难看出,这个处理流程有点类似二分搜索的思想,这样能以最快的速度,最精确地定位出能响应触摸事件的UIView。

响应:

1、如果最终 hit-test没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃;

2、hitTest:withEvent:方法将会忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。






0 0