iOS 事件响应链 & 事件传递链

iOS 事件响应链 & 事件传递链

1. 事件传递链(Event Delivery Chain)

概念

当用户触摸屏幕时,事件首先由 系统(UIApplication) 接收,然后按以下顺序传递:

  1. UIApplication → 2. UIWindow → 3. 根视图控制器(Root ViewController) → 4. 视图层级(UIView)

事件传递流程

当手指触摸屏幕时:

  1. UIApplication 接收到事件,将其交给当前的 keyWindow(UIWindow) 处理。
  2. UIWindow 调用 hitTest(_:with:) 方法,找到触摸点所在的 最底层视图(最精确的 View)
  3. UIView 层级递归执行 hitTest(_:with:),最终返回触摸点所在的 最前面的子视图(即最符合触摸点的视图)。
  4. 如果 找到合适的 View ,事件将传递给这个 View 的 touchesBegan(_:with:) 方法,否则事件被丢弃。

事件传递代码示例

swift 复制代码
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 检查自己是否能接收事件
    if !self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01 {
        return nil
    }
    
    // 检查触摸点是否在当前 View 内
    if self.point(inside: point, with: event) {
        // 倒序遍历子视图,找到最前面的 View
        for subview in self.subviews.reversed() {
            let convertedPoint = subview.convert(point, from: self)
            if let hitView = subview.hitTest(convertedPoint, with: event) {
                return hitView
            }
        }
        return self // 如果没有找到子视图,则自己接收事件
    }
    return nil
}

2. 事件响应链(Responder Chain)

概念

事件传递链 找到目标 UIView 后,事件需要进行 响应处理 。如果该视图无法处理,事件会沿着 响应者链(Responder Chain) 向上传递,直到被某个响应者处理或最终被丢弃。

事件响应链传递路径

UIView 不能处理事件时,事件会沿着以下路径 向上传递

  1. 当前 View(UIView)
  2. View 的父视图(superview)
  3. View 所在的 ViewController(UIViewController)
  4. ViewController 所属的 UIWindow
  5. UIApplication(全局应用对象)
  6. 如果仍未处理,则被丢弃

事件响应链代码示例

swift 复制代码
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("\(self) 处理触摸事件")
    super.touchesBegan(touches, with: event) // 传递给下一个响应者
}

如果当前 View 没有实现 touchesBegan(_:with:) ,则会调用 nextResponder() 方法,将事件传递给 下一个响应者

如何获取 nextResponder()

swift 复制代码
override var next: UIResponder? {
    return self.superview // 下一个响应者是父视图
}

3. 事件传递链 vs 响应链

事件传递链(Event Delivery) 事件响应链(Responder Chain)
作用 负责找到 最适合的 View 处理事件 负责 事件处理,如果无法处理则向上传递
传递方向 UIApplication → UIWindow → 视图层级 当前 View 向上 → 父视图 → 控制器 → UIWindow → UIApplication
核心方法 hitTest(_:with:)pointInside(_:with:) touchesBegan(_:with:)nextResponder()
终点 找到 最前面的 View 找到 能够处理事件的响应者

4. 事件传递 & 响应示例

示例:自定义按钮 MyButton 传递 & 响应事件

swift 复制代码
class MyButton: UIButton {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        print("检查是否可以接收事件: \(self)")
        return super.hitTest(point, with: event)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("MyButton 处理触摸事件")
        super.touchesBegan(touches, with: event)
    }
}

如果 MyButton 无法处理事件 ,会沿着 响应链向上传递 ,直到 UIViewController 处理它。


5. 结论

  • 事件传递链 :确定由 哪个 View 处理事件hitTest(_:with:))。
  • 事件响应链 :确定 如何处理事件 ,如果当前 View 无法处理,则 沿着响应链传递nextResponder())。
  • 事件最终要么被处理,要么被丢弃

这就是 iOS 事件传递和响应机制 ,掌握这个知识点有助于 处理自定义手势、拦截触摸事件、调整事件传递逻辑!🚀

相关推荐
keyipatience1 分钟前
3.Linux基本指令2
前端·html
Hhang1 分钟前
从 ERP 系统出发,我是如何设计一套 LLM 多 Agent 系统的(二)
前端·人工智能·agent
源码老李5 分钟前
Day 07 · 游戏也要管理状态:场景切换·资源加载·对象池实战
前端·javascript·游戏
aidenxian11 分钟前
iOS App 真实包大小:你以为的大小为什么是错的
前端
天才熊猫君13 分钟前
📄 第三篇:Vue 3 命令式弹窗 Provide 污染与关闭动画修复
前端·javascript·vue.js
lxh011315 分钟前
2024春招美团前端
前端
漫游的渔夫19 分钟前
从 Demo 到生产:为什么你的 AI 功能一上线就成了不可控的“黑盒”?
前端·人工智能
天才熊猫君19 分钟前
📄 第一篇:Vue 3 命令式弹窗使用指南
前端·javascript·vue.js
天才熊猫君21 分钟前
📄 第二篇:Vue 3 命令式弹窗 provide/inject 机制解析
前端·javascript·vue.js