iOS Responder chain
- 前言
- 事件的传递及响应过程, 如图:
- `Hit-Test`机制: (寻找响应者)
- `Hit-Test`方法伪实现如下:
前言
iOS中所有继承自UIResponder
的类的实例, 都是可以响应touch
事件的对象. 响应机制分为传递链
和响应链
.
传递链: 由系统向离用户最近的view传递: UIApplication -> UIWindow -> RootViewController -> View -> ... -> Button
响应链: 由离用户最近的view向系统响应: Button -> View -> ... -> RootViewController -> UIWindow -> UIApplication
事件的传递及响应过程, 如图:
向右指的箭头为传递链, 向左指的箭头为响应链.
Hit-Test
机制: (寻找响应者)
- 当发生
touc
h后, 系统会将touch
以UIEvent
的方式, 加入到UIApplication
管理的事件任务队列
中(FIFO
) UIApplication
将出入任务队列最前端的事件向下传递, 传递给UIWindow
UIWindow
将事件向下传递给RootVC
RootVC
将事件向下传递给View
- 调用
View
的hitTest
方法, 判断当前View
是否可响应事件, 再调用pointInside
判断触摸点是否在自己身上,如果都满足就逆序
遍历subViews
, 调用其hitTest
方法 - 若
subViews
中有返回对象的, 则表示该对象为事件的响应者(子视图返回非空对象) - 若
subViews
中都没有返回对象, 则该view
及为时间的响应者(子视图遍历完毕)
Hit-Test
方法伪实现如下:
代码语言:javascript复制override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
print("hitTest: (self)")
/// 1. 判断当前view是否可响应
guard isUserInteractionEnabled else {
/// 不允许用户交互
return nil
}
guard !isHidden else {
/// 已隐藏
return nil
}
guard alpha > 0.01 else {
/// 不透明度小于等于 0.01
return nil
}
/// 2. touch的坐标是否在view的frame内
guard self.point(inside: point, with: event) else {
return nil
}
/// 3. 倒序遍历子视图, 递归调用hitTest
for subview in subviews.reversed() {
let subPoint = self.convert(point, to: subview)
/// 首个非空子视图, 即为 first responder
if let fitView = subview.hitTest(subPoint, with: event) {
return fitView
}
}
/// 4. 遍历所有的子视图都没有响应 hit-testing, 则该view为 first responder
return self
}
GitHub Demo
参考: Using responders and the responder chain to handle events