跳到主要内容

事件响应链

问题

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 收到 touchesCancelled
  • delaysTouchesBegan = false:手势识别过程中,View 仍会收到 touchesBegan

常见面试问题

Q1: 事件传递和事件响应的方向有什么不同?

答案

  • 事件传递(Hit-Testing):自上而下,UIApplication → UIWindow → RootView → SubView,寻找最佳响应者
  • 事件响应(Responder Chain):自下而上,从 First Responder 沿响应链向上传递,直到有对象处理

Q2: 哪些情况下 View 无法接收事件?

答案

  1. userInteractionEnabled = false
  2. hidden = true
  3. alpha <= 0.01
  4. 超出父视图 bounds(默认不响应,需重写 hitTest)
  5. 父视图以上任一层不满足条件

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 {
// 根据条件判断
}

相关链接