用户点击按钮的事件处理完整流程文档,详细描述了 iOS 中用户点击按钮的事件处理传递与流程,包括 UIEvent 0
和 UIEvent 1
的区别以及其他相关内容。
iOS 用户点击按钮的事件处理流程
目录
1. 用户交互开始
当用户在屏幕上点击一个按钮(UIButton
)时,触发了一个触摸事件。iOS 将这些触摸事件以 UITouch
的形式封装,并通过触摸系统进行处理。
1.1. 触摸事件的输入
- 当用户的手指接触屏幕,操作系统会生成一个
UITouch
对象,并将其封装在UIEvent
中。 - 这个
UIEvent
对象通常会封装为UIEventType.touches
类型,可能包含多个触摸事件。 - 事件会标识为
UIEvent 0
或UIEvent 1
,具体取决于事件的状态和类型。
2. UIEvent 0 和 UIEvent 1 的区别
- UIEvent 0: 通常表示新的触摸事件序列的开始,例如用户首次触及屏幕。这表示触摸事件的初始状态,视图的高亮状态开始更新,准备响应用户的交互。
- UIEvent 1: 通常表示在已开始的触摸序列中的后续事件,例如用户移动或释放手指。这些事件属于同一触摸序列,iOS 在这些事件中更新触摸的状态,但不代表新的事件序列的开始。
3. 运行循环(Run Loop)
运行循环是 iOS 应用的核心机制,负责监控事件的发生并调度处理。主线程的运行循环处理用户界面事件,而后台线程则处理异步任务。
3.1. 运行循环的工作原理
- 在主线程中,运行循环不断循环执行,等待事件到来,检查定时器、输入源等。
- 当触摸事件到达时,运行循环会被唤醒以处理这些事件,通常在空闲状态下运行,直到有事件被添加进来。
3.2. 唤醒过程
- 事件唤醒: 当用户触摸屏幕时,操作系统将触摸事件通知到主线程的运行循环。运行循环会从待处理状态被唤醒以处理新的事件。
- 事件处理: 唤醒后的运行循环将取出事件,并将其分发到相应的处理程序。
4. 事件的分发
4.1. 触摸事件的传递
一旦创建了触摸事件,UIApplication
会将其分发到相应的视图层次结构中。
- 事件捕获: 当运行循环检测到触摸事件时,会调用
UIApplication
的sendEvent(_:)
方法,将事件传递给应用程序。
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. 总结
整个点击按钮的事件处理流程可以总结为:
- 用户点击: 用户的触摸生成事件并添加到主线程的运行循环。
- 运行循环: 运行循环被唤醒,并检测到事件,调用
sendEvent(_:)
方法分发事件。 - 命中测试: 通过命中测试确定响应视图。
- 响应链: 事件通过响应链传递到
UIButton
,触发相应的触摸处理方法。 - 发送动作: 在
touchesEnded
中触发目标-动作模式,执行相关操作。 - 最终处理: 处理用户交互的逻辑,完成事件的响应。
通过理解运行循环、事件传递链和响应链的相互作用,开发者可以更好地把握 iOS 的事件处理机制,从而设计出更流畅和高效的用户界面。