iOS的事件响应链

好的,iOS 的事件响应链是面试中的高频考点,它考察的是对 UIKit 事件传递机制的理解。下面我将为你详细解析。


一、什么是事件响应链?

**事件响应链** 是 iOS 中用来寻找和处理**触摸事件、晃动事件、远程控制事件**等的一个机制。它是由一系列**响应者对象**连接起来的链条。

核心概念:响应者对象

  • 任何继承自 `UIResponder` 的对象都是响应者

  • 常见的响应者:

  • `UIApplication`

  • `UIWindow`

  • `UIViewController`

  • `UIView`(及其子类,如 `UILabel`, `UIButton` 等)


二、响应链的建立:视图层级

响应链是基于应用的视图层级建立的,遵循 **从下到上** 的规则:

```

UIApplication → UIWindow → Root ViewController → 父视图 → ... → 子视图

```

**查找下一个响应者的规则**:

  1. **UIView**:
  • 如果它有视图控制器,下一个响应者是它的视图控制器

  • 否则,下一个响应者是它的父视图

  1. **UIViewController**:
  • 如果它的视图是 window 的根视图,下一个响应者是 window

  • 否则,下一个响应者是它的父视图控制器(如果有)或父视图

  1. **UIWindow**:下一个响应者是 `UIApplication`

  2. **UIApplication**:下一个响应者是 `AppDelegate`(如果它继承自 `UIResponder`)


三、事件传递的完整流程

事件处理分为两个主要阶段:

阶段 1:事件传递(Hit-Testing) - 寻找第一响应者

**目标**:找到最合适处理触摸事件的视图(第一响应者)。

**过程**:

  1. 系统从 `UIWindow` 开始,调用 `hitTest:withEvent:` 方法

  2. `hitTest:withEvent:` 内部会调用 `pointInside:withEvent:` 判断触摸点是否在自己范围内

  3. 如果在范围内,它会**从后往前**遍历自己的子视图(后添加的视图在顶层):

  • 对每个子视图递归调用 `hitTest:withEvent:`
  1. 最终返回最深层的、包含触摸点、且 `userInteractionEnabled = YES`、`alpha > 0.01` 的视图

**示例代码理解**:

```objectivec

// 伪代码逻辑

  • (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

// 1. 检查自身条件

if (self.userInteractionEnabled == NO ||

self.hidden == YES ||

self.alpha <= 0.01) {

return nil;

}

// 2. 检查触摸点是否在自身范围内

if ([self pointInside:point withEvent:event] == NO) {

return nil;

}

// 3. 从后往前遍历子视图

for (UIView *subview in [self.subviews reverseObjectEnumerator]) {

CGPoint convertedPoint = [self convertPoint:point toView:subview];

UIView *hitView = [subview hitTest:convertedPoint withEvent:event];

if (hitView) {

return hitView; // 找到了合适的子视图

}

}

// 4. 没有合适的子视图,自己就是第一响应者

return self;

}

```

阶段 2:事件响应 - 沿着响应链传递

**目标**:如果第一响应者不处理事件,就将事件传递给下一个响应者。

**过程**:

  1. 事件首先发送给**第一响应者**

  2. 如果第一响应者不处理,就传递给它的**下一个响应者**

  3. 继续传递,直到有响应者处理事件,或者到达 `UIApplication` 仍不处理,事件被丢弃

**响应者处理方法**:

```objectivec

// 触摸事件

  • (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

  • (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

  • (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

  • (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;

// 其他事件

  • (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event; // 晃动

  • (void)remoteControlReceivedWithEvent:(UIEvent *)event; // 远程控制

```


四、实际场景示例

假设有这样的视图层级:

```

UIWindow

└── RootViewController.view (红色)

├── View A (蓝色, 200x200)

│ └── Button B (黄色, 100x100)

└── View C (绿色, 200x200)

```

**场景 1:点击 Button B**

  1. **Hit-Testing**:
  • `UIWindow` → `RootViewController.view` → `View A` → `Button B`

  • 第一响应者:`Button B`

  1. **事件响应**:
  • 如果 `Button B` 处理了点击(有 target-action),事件处理结束

  • 如果 `Button B` 不处理,传递给 `View A` → `RootViewController` → `UIWindow` → `UIApplication`

**场景 2:点击 View C 的空白区域**

  1. **Hit-Testing**:
  • `UIWindow` → `RootViewController.view` → `View C`

  • 第一响应者:`View C`

  1. **事件响应**:
  • `View C` 的 `touchesBegan:` 被调用

五、常见面试问题与答案

Q1:如果子视图超出了父视图的 bounds,还能接收到事件吗?

**A**:默认情况下**不能**。因为 `pointInside:withEvent:` 在判断时,如果触摸点在父视图的 bounds 之外,会直接返回 NO,就不会继续向子视图进行 Hit-Testing。

**解决方案**:重写父视图的 `pointInside:withEvent:` 方法

```objectivec

  • (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {

// 扩大点击区域,包含所有子视图

CGRect expandedBounds = CGRectInset(self.bounds, -50, -50);

return CGRectContainsPoint(expandedBounds, point);

}

```

Q2:如何阻止某个视图接收触摸事件?

**A**:有几种方式:

  • 设置 `userInteractionEnabled = NO`

  • 设置 `hidden = YES`

  • 设置 `alpha <= 0.01`

  • 重写 `hitTest:withEvent:` 返回 nil

Q3:`touchesBegan` 和 `UIControl` 的 target-action 哪个先执行?

**A**:`UIControl` 的 target-action 机制是基于响应链的更高级封装。当点击 `UIButton` 时:

  1. 先调用 `touchesBegan` 等方法

  2. 然后 `UIButton` 内部处理会触发 target-action

  3. 如果重写了 `touchesBegan` 但不调用 `super`,可能会阻止 target-action 的执行


六、响应链的实际应用

  1. **自定义事件传递**:重写 `hitTest:withEvent:` 可以实现不规则形状点击

  2. **事件拦截**:在父视图层拦截所有子视图的事件

  3. **全局手势处理**:在 `UIWindow` 或根视图控制器处理特定事件

  4. **调试事件问题**:理解响应链可以帮助快速定位事件不响应的问题

掌握事件响应链机制,对于处理复杂的 UI 交互、自定义控件和调试触摸相关问题都非常有帮助。

相关推荐
1telescope13 小时前
MacBook 安装 nvm 管理 Node.js 多版本教程
macos·node.js
1telescope14 小时前
MacBook 安装 Oh My Zsh 完整教程
macos·mac
蜜汁小强14 小时前
macOS 上的git代理配置在哪里
git·macos·代理模式·proxy模式
蜜汁小强15 小时前
macOS 上升级到 python 3.12
开发语言·python·macos
上天_去_做颗惺星 EVE_BLUE15 小时前
Android设备与Mac/Docker全连接指南:有线到无线的完整方案
android·linux·macos·adb·docker·容器·安卓
2501_9160088916 小时前
iOS开发APP上架全流程解析:从开发到App Store的完整指南
android·ios·小程序·https·uni-app·iphone·webview
goodmao16 小时前
【macOS】【磁盘空间整理】查看大文件夹
macos
七夜zippoe16 小时前
Cython终极性能优化指南:从Python到C++的混合编程实战
c++·python·macos·cython·类型系统·内存视图
2501_915909061 天前
Charles 抓不到包怎么办?iOS 调试过程中如何判断请求路径
android·ios·小程序·https·uni-app·iphone·webview
2501_916007471 天前
iOS和iPadOS文件管理系统全面解析与使用指南
android·ios·小程序·https·uni-app·iphone·webview