当你设计你的应用程序时,你可能需要动态的响应事件。例如,一个触摸事件可以由屏幕上的很多对象产生,你必须决定由哪个对象响应对应的事件和明白该对象如何接收到事件的。
当用户产生了一个事件,UIKit创建了一个包含处理事件所需信息的事件对象。然后把这个事件对象放置在全局事件队列。对于触摸事件,会被包装成一个UIEvent对象。对于动作事件,会根据你所使用的框架和你感兴趣的动作事件而有所不同。
一个事件沿着一个具体的路径向前传递,直到有一个对象可以对其进行处理。首先,
UIApplication单例对象会从事件队列里获取一个事件并把它分发,一贯地将事件发送到应用的key window对象,key window对象会把事件传递到原始对象进行处理,原始对象会根据事件的类型而有所不同。
· 触摸事件。对于触摸事件,window对象首先会尝试传递事件到产生触摸事件的视图。这个视图被称为hit-test视图,寻找hit-test视图的过程被称为hit-testing。
· 动作和远程控制事件。window对象会把摇一摇动作或者远程控制事件发送到第一响应者进行处理。
这些事件路径的最终目标是寻找一个对象可以处理和相应一个事件。因此,UIKit首先会事件发送到最适合处理该事件的对象。最适合处理触摸事件的对象就是hit-test视图,对于其他事件,对象就是第一响应者。接下来的部分会详细展开hit-test视图和第一响应者对象是如何被确认的。
Hit-Testing返回触摸事件发生的视图
iOS利用hit-tesing寻找被触摸的视图。hit-tesing需要检查一个触摸事件是否在相关的视图对象的范围内,如果是,它会递归地检查这个视图对象的所有子视图。处于视图层级中包含触摸点的最底层视图就会成为hit-test视图。当iOS确定hit-test视图后,它会把触摸事件抛给这个视图去处理。
例如,假如用户触摸视图E(图2-1)。iOS通过以下顺序从子视图中找出hit-test视图
1.触摸点在视图A里面,所以会检查子视图B和C。
2.触摸点不在视图B的范围内,但是在视图C的范围内,所以它会检查子视图D和E。
3.触摸点不在视图D,但在视图E的范围内。
视图E是在视图层级中包含触摸点的最底层视图,所以它就成为了hit-test视图
(图2-1)
(hitTest:withEvent:)方法返回一个给定CGPoint和UIEvent的hit-test视图。(hitTest:withEvent:)方法通过(pointInside:withEvent:)方法调用。如果一个点通过(hitTest:withEvent:)方法判断是在视图的范围内,(pointInside:withEvent:)方法就会返回YES。然后这个方法就会在返回YES的子视图上面递归地调用(hitTest:withEvent:)方法。
如果这个点通过(hitTest:withEvent:)方法判断不在视图的范围内,最先调用的(pointInside:withEvent:)方法就会返回NO。这个点被忽略了,(hitTest:withEvent:)方法就会返回nil。如果一个子视图返回NO,那么在视图层级中整个分支都会被忽略,因为如果这个触摸点不在这个视图中产生,那么也不会再这个视图的子视图中产生。这就意味着一些在父视图外的子视图的点不能接收到触摸事件,因为触摸点必须在父视图和子视图的范围内。子视图的范围超出父视图的情况会发生在子视图的clipsToBounds属性被设为NO。
注意:一个触摸对象的生命周期会跟它的hit-test密切关联,即使后来触摸移到view的外面。
hit-test视图有第一次机会去处理一个触摸事件。如果hit-test视图不能处理一个事件,这个事件就会在视图的响应者链条上传递,直到系统找到一个可以找到该事件的对象。
响应者链条由响应者对象组成
很多类型的事件依赖一个响应者链条进行传递。响应者链条是一系列的响应者对象。由第一个响应者开始,并由application对象结束。如果第一响应者不能处理一个事件,事件会向前传递给响应者链条的下一个响应者。
响应者对象是一个可以处理事件的对象。UIResponder类是所有响应者对象的基类,它定义的程序接口不只是为了事件的处理,而且为了普通的响应者行为。UIApplication,UIViewController和UIView的实例都可以成为响应者,意味着所有的视图和很多的关键控制器对象都可以是响应者。注意:核心动画图层都不可以成为响应者。
首先由第一响应者接收事件。一般的,第一项响应者是一个视图对象。一个对象通过以下两种方式成为第一响应者:
1、复写(canBecomeFirstResponder)方法并返回YES。
2、接收一个(becomFirstResponder)信息。如果有必要,一个对象可以发送这条信息给它自己。
注意:一个对象成为第一响应者前确保应用已经把这个对象的图层渲染出来。例如,我们一般在(viewDidAppear:)方法里面调用(becomeFirstResponder)方法,如果尝试在(viewWillAppear:)方法里面分配第一响应者,对象的图层还没有渲染出来,所以(becomeFirstResponder)方法就会返回NO。
事件不是唯一的依赖响应者链条的对象。响应者链条被用于以下的情况:
· 触摸事件。如果hit-test视图不能处理一个触摸事件,那么这个事件就会从hit-test视图开始沿着响应者链条传递。
· 动作事件。通过UIKit来处理摇一摇动作事件,第一响应者必须实现UIResponder类的(motionBegan:withEvent:)方法或者(motionEnded:withEvent:)方法之一。
· 远程控制事件。第一响应者处理远程控制事件必须实现UIResponder类的(remoteControlReceivedWithEvent:)方法。
· 动作信息。