事件响应链
问题
iOS 的事件传递和响应链是怎样的?Hit-Testing 的过程?手势识别器与响应链的关系?
答案
事件传递 vs 事件响应
- 事件传递(Hit-Testing):从 UIWindow → 子视图,找到最合适的 View(自上而下)
- 事件响应(Responder Chain):从最佳 View → 父视图 → VC → Window → App(自下而上)
Hit-Testing 过程
hitTest:withEvent: 方法的查找逻辑:
// 系统实现伪代码
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard isUserInteractionEnabled, !isHidden, alpha > 0.01 else { return nil }
guard self.point(inside: point, with: event) else { return nil }
// 倒序遍历(后添加的视图在上层,优先响应)
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return self
}
扩大点击热区
class LargeHitButton: UIButton {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// 将点击区域向外扩大 10pt
let expandedBounds = bounds.insetBy(dx: -10, dy: -10)
return expandedBounds.contains(point)
}
}
让超出父视图范围的子视图响应事件
class ExpandableView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 正常流程
let hitView = super.hitTest(point, with: event)
if hitView != nil { return hitView }
// 检查超出范围的子视图
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
let result = subview.hitTest(convertedPoint, with: event)
if result != nil { return result }
}
return nil
}
}
响应者链(Responder Chain)
Target View → Super View → ... → Root View → VC → UIWindow → UIApplication → UIApplicationDelegate
每个 UIResponder 子类都可以重写以下方法来处理触摸事件:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { }
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { }
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { }
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { }
手势识别器与响应链
手势识别器(UIGestureRecognizer)在响应链之前截获事件:
默认行为
cancelsTouchesInView = true:手势识别成功后,View 收到touchesCancelleddelaysTouchesBegan = false:手势识别过程中,View 仍会收到touchesBegan
常见面试问题
Q1: 事件传递和事件响应的方向有什么不同?
答案:
- 事件传递(Hit-Testing):自上而下,UIApplication → UIWindow → RootView → SubView,寻找最佳响应者
- 事件响应(Responder Chain):自下而上,从 First Responder 沿响应链向上传递,直到有对象处理
Q2: 哪些情况下 View 无法接收事件?
答案:
userInteractionEnabled = falsehidden = truealpha <= 0.01- 超出父视图 bounds(默认不响应,需重写 hitTest)
- 父视图以上任一层不满足条件
Q3: 如何解决手势冲突?
答案:
// 1. 要求一个手势失败后另一个才生效
panGesture.require(toFail: swipeGesture)
// 2. UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true // 允许同时识别
}
// 3. shouldBegin 控制是否开始识别
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// 根据条件判断
}