简单来说就是 :一级一级的找到响应的视图,如果没有就传给UIWindow实例和UIApplication实例,要是他们也处理不了,就丢弃这次事件...
对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:
1、触屏事件(Touch Event)
2、运动事件(Motion Event)
3、远端控制事件(Remote-Control Event)
响应者链条概念: iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队 列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用 hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。
响应者对象(Responder Object) 指的是 有响应和处理事件能力的对象。 响应者链就是由一系列的响应者对象 构成的一个层次结构。
UIResponder 是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的 UIApplication、 UIViewController、 UIWindow 和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们 的实例都是可以构成响应者链的响应者对象。
UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用 pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果 pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。
hitTest:withEvent:方法的处理流程如下:
首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
若返回NO,则hitTest:withEvent:返回nil;
若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。
假如用户点击了View E,下面结合图二介绍hit-test view的流程:
1、A是UIWindow的根视图,因此,UIWindwo对象会首相对A进行hit-test;
2、显然用户点击的范围是在A的范围内,因此, pointInside:withEvent:返回了YES,这时会继续检查A的子视图;
3、这时候会有两个分支,B和C:
点击的范围不再B内,因此B分支的 pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;
点击的范围在C内,即C的 pointInside:withEvent:返回YES;
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。
***上面找到了事件的第一响应者,接下来就该沿着寻找第一响应者的相反顺序来处理这个事件,如果UIWindow单例和UIApplication都无法处理这一事件,则该事件会被丢弃。***
说明:
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:方法来处理这种情况。
3、我们可以重写 hitTest:withEvent:来达到某些特定的目的,下面的链接就是一个有趣的应用举例,当然实际应用中很少用到这些。
再深入详细一些:
iOS的事件大致分为三种:触摸事件、加速计事件、远程控制事件
首先要理解以下几个概念:
一、响应者对象:
在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”。
UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。
二、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;
三、UITouch
用户同时触摸的手指,每一根就代表一个UITouch对象,它保存着跟手指相关的信息,比如触摸的位置、时间、阶段。
当手指移动(move)时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置。
当手指离开(end)屏幕时,系统会销毁相应的UITouch对象.
四、UIEvent
触摸事件发生,必须会产生一个UIEvent对象,称为事件对象,记录事件产生的时刻和类型。
五、首先处理事件的响应者
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
touchesBegan…
touchesMoved…
touchedEnded…
下面是PPT示例,说的很明白。
UIView不接收触摸事件的三种情况
1.alpha= 0.0 ~ 0.01
2.hidden属性=YES
3.userInteractionEnabled=YES
注意:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的
六、响应者链条
合适的控件调用touches方法后,会默认地将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理。
寻找上一个响应者的过程,看起来是寻找首要合适控件的逆过程。
当前响应者的touches方法中如果调用[super touches],就会对用上一个响应者touches方法,同时可以将UITouch对象和UIEvent对象向上传递。
这样就能够将一次触摸事件的事件对象和触摸对象的信息传递给多个响应者。
问题:上一个响应者(nextResponder)是谁?
判断步骤:
1>如果当前的view是控制器的view,控制器就是上一个响应者
2>如果当前的view不是控制器的view,那么父控件就是上一个响应者
3>如果当前响应者是控制器,那么上一个响应者是UIWindow;如果UIWindow也不处理,就再往上传给UIApplication
4>如果UIApplication也不处理,那么这条消息就被会废弃
七、触摸事件处理过程
1.用户触摸后,系统先将事件对象(event)由上往下传递(父控件传给子控件),找到最合适的控件来处理事件(递归查找当前控件的最适合子控件)
2.调用合适控件的touches相关方法
3.如果调用了super的touches相关方法,就会将事件顺着响应者链条往上传递,传给上一个响应者
4.接着就会调用上一个响应者的touches方法
5.只要当前响应者的touches方法中调用了super的touches方法,还会继续往上递归调用,直到不再调用super的touches方法