iOS 用户点击按钮的事件处理完整流程

166 阅读6分钟

用户点击按钮的事件处理完整流程文档,详细描述了 iOS 中用户点击按钮的事件处理传递与流程,包括 UIEvent 0UIEvent 1 的区别以及其他相关内容。


iOS 用户点击按钮的事件处理流程

目录

  1. 用户交互开始
  2. UIEvent 0 和 UIEvent 1 的区别
  3. 运行循环(Run Loop)
  4. 事件的分发
  5. 事件处理
  6. 发送动作与事件响应
  7. 事件的最终处理
  8. 总结

1. 用户交互开始

当用户在屏幕上点击一个按钮(UIButton)时,触发了一个触摸事件。iOS 将这些触摸事件以 UITouch 的形式封装,并通过触摸系统进行处理。

1.1. 触摸事件的输入

  • 当用户的手指接触屏幕,操作系统会生成一个 UITouch 对象,并将其封装在 UIEvent 中。
  • 这个 UIEvent 对象通常会封装为 UIEventType.touches 类型,可能包含多个触摸事件。
  • 事件会标识为 UIEvent 0UIEvent 1,具体取决于事件的状态和类型。

2. UIEvent 0 和 UIEvent 1 的区别

  • UIEvent 0: 通常表示新的触摸事件序列的开始,例如用户首次触及屏幕。这表示触摸事件的初始状态,视图的高亮状态开始更新,准备响应用户的交互。
  • UIEvent 1: 通常表示在已开始的触摸序列中的后续事件,例如用户移动或释放手指。这些事件属于同一触摸序列,iOS 在这些事件中更新触摸的状态,但不代表新的事件序列的开始。

3. 运行循环(Run Loop)

运行循环是 iOS 应用的核心机制,负责监控事件的发生并调度处理。主线程的运行循环处理用户界面事件,而后台线程则处理异步任务。

3.1. 运行循环的工作原理

  • 在主线程中,运行循环不断循环执行,等待事件到来,检查定时器、输入源等。
  • 当触摸事件到达时,运行循环会被唤醒以处理这些事件,通常在空闲状态下运行,直到有事件被添加进来。

3.2. 唤醒过程

  • 事件唤醒: 当用户触摸屏幕时,操作系统将触摸事件通知到主线程的运行循环。运行循环会从待处理状态被唤醒以处理新的事件。
  • 事件处理: 唤醒后的运行循环将取出事件,并将其分发到相应的处理程序。

4. 事件的分发

4.1. 触摸事件的传递

一旦创建了触摸事件,UIApplication 会将其分发到相应的视图层次结构中。

  • 事件捕获: 当运行循环检测到触摸事件时,会调用 UIApplicationsendEvent(_:) 方法,将事件传递给应用程序。
func sendEvent(_ event: UIEvent) {
    // 处理事件
    self.dispatchEvent(event)
}
  • 命中测试: dispatchEvent(_:) 方法负责命中测试,以确定哪个视图将接收该事件。命中测试通过调用视图的 hitTest(_:with:) 方法进行。
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 判断当前视图是否可接收触摸
    if !isUserInteractionEnabled || isHidden || alpha < 0.01 {
        return nil
    }

    // 如果触摸点在视图的 bounds 中
    if bounds.contains(point) {
        // 遍历子视图,寻找可以响应触摸的子视图
        for subview in subviews.reversed() {
            let convertedPoint = subview.convert(point, from: self)
            if let hitView = subview.hitTest(convertedPoint, with: event) {
                return hitView
            }
        }
        // 如果没有子视图响应,返回当前视图
        return self
    }
    return nil
}

4.2. 找到响应视图

一旦确定了触摸事件的目标视图(如 UIButton),该视图便会开始处理事件。

5. 事件处理

5.1. 响应链(Responder Chain)

当事件到达 UIButton 时,事件会被传递到该视图及其父视图中。响应链是处理事件的机制之一,允许事件在视图之间传播。响应链的成员包括:

  • UIWindow
  • UIViewController
  • UIView

当事件到达 UIView 时,以下方法会被调用:

  • touchesBegan(_:with:)
  • touchesMoved(_:with:)
  • touchesEnded(_:with:)
  • touchesCancelled(_:with:)

如果该视图无法处理该事件,事件会继续向上传递到下一个响应者(即父视图)。

5.2. UIButton 的事件处理

UIButton 中,触摸事件将经历以下步骤:

  • Touches Begin: touchesBegan(_:with:) 被调用,更新按钮的高亮状态。
  • Touches Moved: 如果用户在按钮上滑动,touchesMoved(_:with:) 被调用。
  • Touches Ended: 当用户放开手指,touchesEnded(_:with:) 被调用,按钮确认点击并发送动作。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    // 更新按钮状态为高亮
    self.isHighlighted = true
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)
    // 触发按钮的动作
    sendActions(for: .touchUpInside)
    // 复位按钮状态
    self.isHighlighted = false
}

6. 发送动作与事件响应

touchesEnded 中,UIButton 通过 sendActions(for:) 方法发送 .touchUpInside 事件,触发与按钮关联的目标-动作模式。这一过程确保按钮的点击操作被正确处理。

button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

@objc func buttonTapped() {
    print("Button was tapped!")
}

7. 事件的最终处理

一旦动作被触发,所有与按钮相关的操作(如执行 IBAction 方法)将被执行。这是整个事件处理的最终结果,用户交互的逻辑在这里得以实现。

8. 总结

整个点击按钮的事件处理流程可以总结为:

  1. 用户点击: 用户的触摸生成事件并添加到主线程的运行循环。
  2. 运行循环: 运行循环被唤醒,并检测到事件,调用 sendEvent(_:) 方法分发事件。
  3. 命中测试: 通过命中测试确定响应视图。
  4. 响应链: 事件通过响应链传递到 UIButton,触发相应的触摸处理方法。
  5. 发送动作: 在 touchesEnded 中触发目标-动作模式,执行相关操作。
  6. 最终处理: 处理用户交互的逻辑,完成事件的响应。

通过理解运行循环、事件传递链和响应链的相互作用,开发者可以更好地把握 iOS 的事件处理机制,从而设计出更流畅和高效的用户界面。