内容概览
- 综述
- 确定一个事件的第一响应者(First Responder)
- 确定哪个响应者包含一个触控事件
- 改变响应者链
- 在视图(UIView)中处理触控事件
综述
iOS 应用使用响应者对象接收和处理事件。一个响应者对象是 UIResponder
类的实例,常见的子类包括: UIView
, UIViewController
, UIApplication
。响应者接收原始的事件数据,并且必须对其进行 处理 或者将其 转发给另一个响应者对象。当你的应用接收到一个事件时,UIKit
自动将这个事件传递给最适合处理这个事件的响应者对象 —— 第一响应者(first responder
)。
未被处理的事件会在响应链中的响应者之间传递,这是应用中的响应者对象的动态配置。
上图展示了一个应用中的响应者,还显示了事件如何按照响应者链从一个响应者传递到下一个响应者。
如果 text field 不处理事件,则 UIKit 会将事件发送到 text field 的 super view 对象,然后是 window 的 root view。
从 root view 开始,响应链在将事件传递到 window 之前,先传递到 root view 所属的 view controller。
如果 window 无法处理事件,则 UIKit 会将事件传递给 UIApplication 对象,如果该 UIApplication 对象是 UIResponder 实例并且还不是响应者链的一部分,则可能传递给应用程序委托。
确定一个事件的第一响应者(First Responder)
UIKit 根据事件的类型将对象指定为事件的第一响应者。
事件类型 | 第一响应者 |
---|---|
触控事件 | 发生触控事件的视图 |
按下设备的物理按键触发的事件 | 有焦点的对象 |
摇晃动作事件 | 由 UIKit 或者开发者指定 |
远程控制事件 | 由 UIKit 或者开发者指定 |
编辑菜单消息 | 由 UIKit 或者开发者指定 |
与加速度计、陀螺仪和磁力计有关的运动事件不遵循响应程序链。
相反,Core Motion
将这些事件直接传递到指定的对象。
有关更多信息,请参见 Core Motion Framework。
定义事件类型的代码:
public enum EventType : Int {
// 点击屏幕相关的事件
case touches
// 设备移动相关的事件,如:用户摇晃设备
case motion
// 来自控制设备多媒体的外部配件,如:耳机
case remoteControl
// 按下设备的物理按键
@available(iOS 9.0, *)
case presses
}
控件(UIControl)使用动作(action)消息直接与其关联的目标对象进行通信。
// 为特定事件添加 target/action。你可以多次调用,也可以为特定事件指定多个 target/action
// 当 target 为nil时,事件沿着响应者链传递。action 可能会包括发送者和事件的顺序
// action 不能为NULL。target 不会被强引用
func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event)
当用户与控件交互时,控件会将操作消息发送到其目标对象(target)。
动作消息不是事件,但它们仍可以利用响应者链。当控件的目标对象为 nil
时,UIKit
从目标对象开始并遍历响应者链,直到找到实现适当操作方法的对象为止。
例如,UIKit
编辑菜单使用此行为来搜索响应者对象,这些对象实现了诸如 cut(_:), copy(_:), or paste(_:) 之类的方法。
手势识别器(Gesture recognizer) 会在视图处理事件之前接收触摸(touch)和按下(press)事件。
如果视图的手势识别器无法识别一系列触摸,则 UIKit
会将触摸发送给视图。
如果视图无法处理触摸,则 UIKit
会将其向上传递到响应者链。
有关使用手势识别器处理事件的更多信息,请参见处理 Handling UIKit Gestures。
确定哪个响应者包含一个触控事件
UIKit
使用基于视图的点击测试来确定触摸事件发生的位置。
具体来说,UIKit
将触摸位置与视图层次结构中视图对象的边界(bounds)进行比较。
UIView
的 func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
方法遍历视图层次结构,查找包含指定触摸的最深的子视图,该子视图会成为触摸事件的第一响应者。
如果触摸位置在视图范围(bounds)之外,则
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
方法将忽略该视图及其所有子视图。因此,如果视图的clipsToBounds
属性为false
,则即使该视图正好包含触摸,也不会返回该视图范围(bounds)之外的子视图。
有关点击测试行为的更多信息,请参见UIView中关于hitTest(_:with:)
方法的讨论。
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
方法的文档:
- 讨论解析:
此方法通过调用每个子视图的 point(inside:with:)
方法来遍历视图层次结构,以确定哪个子视图应接收触摸事件。
// 检测该点是否在接收者的 bounds 范围内
func point(inside point: CGPoint, with event: UIEvent?) -> Bool
如果 point(inside:with:)
返回 true
,则将遍历子视图的视图层次结构,直到找到最前面的包含指定点的视图。如果视图不包含该点,则将忽略其视图层次结构的分支。
你很少需要自己调用此方法,但是你可以重写此方法以在子视图中隐藏触摸事件。
此方法将忽略 隐藏的、禁用用户交互的、alpha值小于0.01 的视图对象。
确定点击时,此方法不会考虑视图的内容。即使指定点位于该视图内容的透明部分中,该视图仍然可以被返回。
位于接收者范围之外的点,即使实际上位于接收者子视图内,也不会被点击测试匹配。如果当前视图的 clipsToBounds
属性设置为 false
,并且受影响的子视图超出了视图的范围,则可能发生这种情况。
发生触摸时,UIKit
会创建一个 UITouch
对象并将其与视图关联。
随着触摸位置或其他参数的更改,UIKit
会使用新信息更新相同的UITouch对象。但唯一不变的属性是视图。 (即使触摸位置移到原始视图之外,UITouch 对象的 view 属性中的值也不会更改。)
触摸结束时,UIKit
会释放该 UITouch
对象。
改变响应者链
var next: UIResponder?
您可以通过重写响应者对象的 next
属性来更改响应者链。
如果重写,下一个响应者将会是你返回的对象。
许多 UIKit
类已经重写此属性并返回特定的对象,比如:
UIView
对象。如果 view 是 view controller 的 root view,则下一个响应者是 view controller;否则,下一个响应者是 view 的 super view。-
UIViewController
对象如果 view controller 的 view 是 window 的 root view,则下一个响应者是 window 对象。
如果 view controller B 是由 view controller A present的,则下一个响应者是 view controller A。
UIWindow
对象。window 的下一个响应者是UIApplication
对象。UIApplication
对象。下一个响应者是 app delegate,但仅当 app delegate 是 UIResponder 的实例且不是 view,view controller 或 UIApplication 对象本身时,才是下一个响应者。
在视图(UIView)中处理触控事件
如果你不打算在 UIView
中使用 UIGestureRecognizer
来处理触控事件,你可以使用 UIView
本身来处理。
因为 UIView
继承于 UIResponder
,所以它可以处理多点触控事件和其他类型的事件。
当 UIKit
确定一个事件发生在某个 UIView
上时,它会调用该 view 的 touchesBegan:withEvent:, touchesMoved:withEvent:, 或者 touchesEnded:withEvent: 等方法。
你在 UIView
中重写的这些方法将处理点击事件处理过程中不同的阶段。
点击事件的不同阶段,如下图所示:
当手指(或者苹果笔)点击屏幕时,UIKit
会创建一个 UITouch
对象,然后将其触控位置设置为合适的点,将其 phase
属性设置为 UITouchPhaseBegan
。
当同一个手指在屏幕上移动时,UIKit
会更新该 UITouch
对象的触控位置并将 phase
属性设置为 UITouchPhaseMoved
。
当手指抬起并离开屏幕,UIKit
会将 phase
属性设置为 UITouchPhaseEnded
,然后点击事件结束。
类似地,系统可能会在任何时候取消正在进行的点击事件。比如,电话呼入可以打断应用的执行过程。
这时,UIKit
会通过调用 touchesCancelled:withEvent:
方法通知你的 view。
你可以通过该方法来清理 view 中的数据结构。
UIKit
会为每个点击屏幕的新手指创建 UITouch
对象。这些 UITouch
对象会和当前的 UIEvent
对象一起被传递。UIKit
可以区分来自手指和苹果笔的触控,所以你可以对它们做不同的处理。
默认情况下,一个 view 只接收第一个
UITouch
和相应的事件,即使不只一个手指点击了屏幕。
如果需要接收多个触控,你需要将multipleTouchEnabled
设置为true
。
参考内容:
Using Responders and the Responder Chain to Handle Events
Handling Touches in Your View
转载请注明出处,谢谢~