iOS Responder chain
- 前言
- 事件的传递及响应过程, 如图:
- `Hit-Test`机制: (寻找响应者)
- `Hit-Test`方法伪实现如下:
前言
iOS中所有继承自UIResponder的类的实例, 都是可以响应touch事件的对象. 响应机制分为传递链和响应链.
传递链: 由系统向离用户最近的view传递: UIApplication -> UIWindow -> RootViewController -> View -> ... -> Button
响应链: 由离用户最近的view向系统响应: Button -> View -> ... -> RootViewController -> UIWindow -> UIApplication
事件的传递及响应过程, 如图:

向右指的箭头为传递链, 向左指的箭头为响应链.
Hit-Test机制: (寻找响应者)
- 当发生
touch后, 系统会将touch以UIEvent的方式, 加入到UIApplication管理的事件任务队列中(FIFO) UIApplication将出入任务队列最前端的事件向下传递, 传递给UIWindowUIWindow将事件向下传递给RootVCRootVC将事件向下传递给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


