您的位置:首页 > 移动开发 > IOS开发

iOS 响应者链事件传递和分发

2016-07-04 10:25 549 查看


响应者链和响应事件

在iOS中,由响应者链来对事件进行响应,所有事件响应的类都是UIResponder(响应者抽象类) 的子类,iOS 中所有能响应事件(触摸,晃动,远程事件)的对象都是响应者. 响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。 

比如在一个触摸事件中, 其实也就是一个事件分发的过程. 这个过程可以被分为两个过程, 一个是检测触摸视图(或者说检测第一响应者)的过程, 另一个是触摸事件处理的过程.下面来具体看一下.


检测触摸视图


检测触摸过程

1.发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的队列事件中 

2.UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常会先发送事件给应用程序的主窗口(keyWindow) 

3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件 

4.这些touches方法默认的做法是将事件顺着响应者链条向上传递,将事件叫个上一个相应者进行处理 

5.找到合适的视图控件后,就会调用视图控件的touches方法来作事件的具体处理:touchesBegin… touchesMoved…touchesEnded等, 并不再检测.


检测触摸视图原理

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个UIView对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。 

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

UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view, 即第一响应者。

注: 

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

2.我们可以重写hitTest:withEvent:来达到某些特定的目的,下面的链接就是一个有趣的应用举例,当然实际应用中很少用到这些。 
http://blog.csdn.net/error/404.html?from=http%3a%2f%2fblog.csdn.net%2fzhaoguodongios%2farticle%2fdetails%2f44082821


举例



比如分别点击了图中的两个位置, 他们的过程如下: 

1.UIApplication->window- >rootViewController -> viewA -> ViewB 

2.UIApplication->window- >rootViewController -> viewC -> ViewD (检测到触摸视图)

这里以点击 ViewD 的事件为例: 

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两个分支: 

点击的范围不再E内,因此E的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil; 

点击的范围在D内,即E的pointInside:withEvent:返回YES,由于D没有子视图(也可以理解成对D的子视图进行hit-test时返回了nil),因此,D的hitTest:withEvent:会将D返回,再往回回溯,就是C的hitTest:withEvent:返回D—>>A的hitTest:withEvent:返回E。 

至此,本次点击事件的第一响应者就通过响应者链的事件分发逻辑成功的找到了。


触摸事件处理


触摸事件处理过程

1.在上面检测触摸视图的过程结束后, 找到了第一响应者(First responder), 并且UIWindow对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。 

2.检测到响应者后,实现touchesBegan:withEvent:等可以处理响应的方法,即处理事件。 

3.如果响应者没有处理事件,事件会向下传递。如果当事件沿着响应者链向上回溯至UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃。


举例

以上面 ViewD 的点击为例: 

ViewD -> viewC ->rootView
4000
Controller -> window- > UIApplication

所以事件处理的顺序与触摸检测查询相反。一般的过程如下: 

触摸的子视图 -> view -> viewController -> window -> UIApplication
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: