iOS的框架分为:应用层、触摸层、媒体层、核心服务层、核心系统操作层以及内核和驱动层。
详见https://blog.csdn.net/ScheenDuan/article/details/134274203?spm=1001.2014.3001.5501
其中触摸事件涉及到触摸层中的UIKit中的UIResponse,只有继承了UIResponse的对象才能接受处理事件。
一、事件传递链(命中测试):
事件传递链 用于确定哪个视图应该接收用户触摸或手势事件。当用户与界面发生交互时,系统会沿着视图层次结构从上到下传递触摸事件,并最终找到能够处理该事件的最小子视图。这个过程称为 Hit-Testing,即事件命中测试。
在 iOS 世界中,为了确定视图是否可以与用户交互,视图具有一个名为
hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 的方法,一旦有来自用户的触摸事件,系统将调用此函数来告诉视图是否应该触发触摸事件或手势识别器。
这里先介绍这个相关方法:
- hitTest(_:with:):通过递归调用来确定哪个子视图最终应该接收触摸事件。
- pointInside(:with:):当系统需要确定某个触摸点是否在某个视图内时,会调用 pointInside(:with:)。如果返回 true,表示点在视图内;如果返回 false,则表示点不在视图内。pointInside 只检查当前视图的边界,而不涉及子视图。
这两个方法是包含关系,见如下代码:
Swift
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 检查触摸点是否在当前视图内
if !point(inside: point, with: event) {
return nil
}
if alpha < 0.01 || !isUserInteractionEnabled || isHidden {
return nil
}
// 遍历子视图并递归查找最合适的响应视图
for subview in subviews.reverse() {
if let hitView = subview.hitTest(point, with: event) {
return hitView
}
}
return nil
}
具体的事件传递过程:
- 事件的触发:
• 当用户触摸屏幕时,系统会首先将触摸事件封装为 UIEvent 对象,传递给应用的窗口对象(UIWindow)。
- 从 UIWindow开始传递:
• UIWindow 会调用 hitTest(_:with:) 方法,通过事件位置查找出最适合接收触摸事件的视图。这通常是视图层次结构中的最小的子视图(也称为"命中的视图")。
- 视图层次结构中的传递:
• hitTest(:with:) 从根视图开始,递归检查每个子视图,使用 pointInside(:with:) 方法判断触摸点是否在某个视图内。
• 找到合适的子视图后,它将作为事件的最终处理者。
- 处理事件:
• 如果视图对象能够处理事件,它会响应相关的事件处理方法,如 touchesBegan(:with:)、touchesMoved(:with:) 或 touchesEnded(_:with:)。
- 未处理事件的传递:
• 如果视图不能处理事件,或者选择不处理事件,它会将事件传递给其下一个响应者(next responder)。
• 这个传递过程会沿着响应链向上传递,最终可能传递到视图控制器(UIViewController)、父视图、窗口(UIWindow)、应用程序对象(UIApplication),甚至是系统对象。
总的来说,就是触摸了就生成一个事件,然后事件从父视图传递给子视图(寻找合适的响应视图),在这期间是用hitest方法去递归查找并且配合了point inside函数查看是否响应在了视图的边界范围内,当找到最适合的响应视图后,才会触发类似于touchesBegan之类的事件处理方法。
提一嘴,如果触摸点在视图外的话怎么进行处理?
- 通过重写 pointInside(_:with:) 方法,手动扩展视图的可交互区域。
- 在父视图中手动处理事件。
- 通过手势识别器或控制器全局捕获触摸事件。
- 在 UIViewController 级别处理触摸事件。
- 通过响应链将事件传递给下一个响应者。
二、响应链
响应链 与事件的实际处理有关。当一个视图或控件接收到触摸事件之后,如果它不能处理该事件,它会将事件沿着 响应链 传递给其他对象(如其父视图或控制器)进行处理。响应链是基于 UIResponder 类实现的。
响应链的流程:
• 传递顺序:当一个对象无法处理事件时,事件会传递给它的下一个响应者(即 nextResponder)。如果下一个响应者也无法处理事件,则事件继续向上传递,直到到达根响应者(通常是 UIApplication 或 UIWindow)。
• nextResponder:每个 UIResponder 都有一个 nextResponder 属性,指向下一个响应者(通常是视图的父视图、视图控制器等)。
响应链的流程示例:
-
用户点击按钮,但按钮没有处理事件。
-
按钮会将事件传递给其父视图,父视图再传递给 UIViewController。
-
如果 UIViewController 也不能处理,事件会传递给 UIWindow,最后传递给 UIApplication。
总的来说:响应链 关注的是事件的"处理"过程,即当一个对象无法处理事件时,它将事件传递给谁。它提供了一个灵活的机制,使得即使某个控件无法处理事件,事件也可以通过 nextResponder 传递给其他对象进行处理。
传递链和响应链区别:
- 事件传递链 决定了谁接收事件,负责从顶层到子视图逐层传递事件。
- 响应链 则决定了谁处理事件,如果接收事件的视图无法处理,它可以将事件传递给下一个响应者。