一部iOS设备会产生各类各样的事件(UIEvent 实例
)好比:触摸屏幕、远程控制等,这些事件发生了就须要有响应者(UIResponder 实例
)去响应这些事件。这就须要一套事件响应机制。bash
查看UIEventType
的定义,咱们知道有4种事件类型。app
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches,
UIEventTypeMotion,
UIEventTypeRemoteControl,
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),
};
复制代码
其中UIEventTypeTouches
就是触摸手机屏幕产生的事件。UIEventTypeMotion
咱们能接触到的就多是手机的shake,也就是摇晃事件了。UIEventTypeRemoteControl
,UIEventTypePresses
对于手机App开发者通常遇不到。UIEventTypePresses
的名字有必定的迷惑性,其实它指的是物理按键被按下,好比电视的遥控器。ide
除了UIEventTypeTouches
其余事件咱们都难以遇到。ui
能响应事件的都是UIResponder
及其子类。常见的UIResponder
的子类有:UIView
,UIViewController
,UIApplication
和UIApplicationDelegate
spa
下面这张图是Apple官方文档中的图,能够看出事件的响应链与视图的层级关系基本一致。值得注意的是响应链的末端是...->UIWindow->UIApplication->UIApplicationDelegate。一个UIResponder
的nextResponder
指向它的下一个响应者。你能够重写(override)nextResponder方法改变下一个响应者。实际上有些类已经重写了nextResponder,好比一个UIView若是是UIController的根视图,它的nextResponder会指向view controller,不然就会指向它的父视图。详见 Using Responders and the Responder Chain to Handle Events 的Altering the Responder Chain
一节。code
若是顺着响应链没有发现可以响应事件的响应者,那么这个事件就会被忽略。cdn
咱们看到,UIResponder中对应每种事件类型都有对应的事件响应方法,好比对于touch事件来讲有touchesBegan: withEvent:
,touchesMoved: withEvent:
,touchesCancelled: withEvent:
,touchesEnded: withEvent:
,还有对于motion事件来讲有motionBegan: withEvent:
,motionEnded: withEvent:
,motionCancelled: withEvent:
。还有针对其余事件的方法,你能够去UIResponder类里去查看。blog
若是一个UIResponder子类重写(override)了上述所说事件响应方法,那么事件就算这个被这个类的实例响应了。事件就再也不会沿着响应链传递。 一个UIControl(UIButton是UIControl的子类),无论它有没有被增长一个target,事件传递到它这里就终止了。UIControl应该是实现了上面说的几个响应事件的方法。事件
好比一个UIView子类重写了touchesBegan: withEvent:
,touchesMoved: withEvent:
,touchesCancelled: withEvent:
,touchesEnded: withEvent:
,若是有触摸事件传递到这个子类的实例,就会调用这些方法,并中止向下传递。图片
上面说的事件响应链有一个起点,这个起点叫作first responder。每一种事件都有找到或者指定first responder的规则,这里咱们只关心触摸事件(touch event)。
当手机屏幕被触摸,系统将其包装成触摸事件(touch event)并传递给UIApplication,UIApplication传递给UIWindow(也是一个UIView的子类),UIWindow调用hitTest:withEvent:
,获得一个可以响应触摸事件(touch event)的UIView
UIView
的hitTest:withEvent:
方法,会遍历view的层级结构,找到包含特定touch的最深层的子视图,称为触摸事件的first responder。若是first responder不能响应事件,就会沿事件响应链传递这个事件直到被响应或者被忽略。
上面的图片展现了hitTest:withEvent:
是如何实现的。其中几个点咱们须要注意,首先判断的是hidden,userInteractionEnable和alpha这几个属性,而后使用pointInside:withEvent:
判断点击事件是否发生在该UIView内。而后是倒序遍历全部子视图并调用他们的hitTest:withEvent:
方法。
事件传递机制原理的几个应用
一、非矩形可点击区域。好比一个button的一个圆形区域能够被点击,其余区域不可点击。只要重写pointInside:withEvent:
,在圆形区域的点返回YES,不在圆形区域的返回NO。
二、按钮超出父视图。默认状况下,按钮超出父视图的部分是不能够点击的。咱们能够重写父视图的pointInside:withEvent:
方法,让按钮超出父视图的部分也返回YES。
1. Using Responders and the Responder Chain to Handle Events