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 交互、自定义控件和调试触摸相关问题都非常有帮助。

相关推荐
bl4ckpe4ch2 小时前
mac安装burpsuite专业版2025中文教程
macos·网络安全·抓包·burpsuite
猪哥帅过吴彦祖6 小时前
Flutter 系列教程:应用导航 - Navigator 1.0 与命名路由
android·flutter·ios
2501_916008897 小时前
iOS 跨平台开发实战指南,从框架选择到开心上架(Appuploader)跨系统免 Mac 发布全流程解析
android·macos·ios·小程序·uni-app·iphone·webview
心灵宝贝7 小时前
MacX DVD Ripper Pro for Mac v6.8.2 安装教程|MacDVD转换软件怎么安装?
macos
鹏多多8 小时前
flutter图片选择库multi_image_picker_plus和image_picker的对比和使用解析
android·flutter·ios
Kapaseker1 天前
Swift 构建 Android 应用?它来了
ios·swift
嚴寒1 天前
我在 Mac 用一行脚本征服了 5TB NTFS:拒绝付费,彻底搞定免费方案
macos·mac
前端架构师-老李1 天前
Maven安装以及环境变量配置(macOS)
java·macos·maven