UIEvent 是事件容器(event container)。当系统收到一个输入(触摸、按压、摇动等)时,会创建一个 UIEvent 对象,把当前这一批事件的状态封装起来,并交给 UIApplication 进行分发。
数据结构与类型
UIEvent 对象内部主要组成是:
1、type: UIEvent.EventType(事件主类型):
常用值:.touches 手指触摸事件、.motion 设备运动(摇动)、.remoteControl 耳机线控、.presses 硬件按钮(如 Home/音量等)、.scroll, .hover(iPadOS 指针事件)
2、subtype: UIEvent.EventSubtype(仅 motion / remoteControl 有):
常用值:.motionShake、.remoteControlPlay、.remoteControlNextTrack
3、 timestamp 事件发生时间,用来进行手势判断。
常用属性
1、var type: UIEvent.EventType
事件主类型(触摸/按压/motion 等)。先检查它,再读取相应集合。
2、var subtype: UIEvent.EventSubtype
细分子类型,针对 motion 等有用。
3、var timestamp: TimeInterval
事件时间戳(以系统启动时间为基准)。用于计算时序、速度、手势判断。
4、var allTouches: Set<UITouch>?
(当 type == .touches)事件中包含的所有 UITouch 对象集合。注意:包含的是当前 UIEvent 的触点快照。
5、func touches(for view: UIView) -> Set<UITouch>?
返回落在指定 view 上的触点集合(如果你只关心某个 view 的触点可以用这个)。
6、var allPresses: Set<UIPress>? 和 func presses(for view: UIView) -> Set<UIPress>?
(当 type == .presses)类似触摸,但用于硬件按键/遥控器/游戏手柄的按钮事件。
7、var coalescedTouches(for touch: UITouch) -> [UITouch]
(性能/平滑度)返回与该触点相关的合并触点样本(用于获得更高频率的历史轨迹,适用于绘图/手写)。对应的还有 predictedTouches(for:) 用于预测触点(平滑/提高响应)。
8、var modifierFlags: UIKeyModifierFlags(在支持键盘事件时可用)
表示事件发生时的键盘修饰键(Shift/Ctrl/Option 等)。
线程/调度注意:UIKit 的 UIEvent 通常在主线程分发;不要在后台线程直接读取或保留它来处理 UI 更新。
UITouch常用字段和方法
UITouch 是 UIEvent 里最常拿到的对象,常用字段和方法:
1、var phase: UITouch.Phase —— .began/.moved/.stationary/.ended/.cancelled。
2、func location(in view: UIView?) -> CGPoint —— 当前坐标(相对于给定 view)。
3、func previousLocation(in view: UIView?) -> CGPoint —— 前一帧坐标。
4、var timestamp: TimeInterval —— 该触点时间戳。
5、var tapCount: Int —— 连击次数(双击等)。
6、var force: CGFloat / var maximumPossibleForce: CGFloat —— 触控压力(3D Touch / Force Touch / Pencil)。
7、var type: UITouch.TouchType —— .direct/.indirect/.pencil/.stylus 等。
8、func preciseLocation(in view: UIView?) -> CGPoint —— 高精度坐标(可用在 Apple Pencil)。
9、func azimuthAngle(in view: UIView?) -> CGFloat / func altitudeAngle —— Pencil 专用角度信息。
10、var majorRadius: CGFloat —— 触点接触面积近似值。
Touch 与 Event 关系
UIEvent 本身不记录触点细节,触点由 UITouch 承载。
UIEvent 只提供访问入口:
func touches(for view: UIView) -> Set<UITouch>?
func allTouches() -> Set<UITouch>?
UITouch 包含坐标、阶段(began/moved/ended/cancelled)、点击次数等。
UIKit 事件传递流程
事件从底层到上层,过程如下:
1、IOKit 把硬件输入送到 SpringBoard / 应用进程
2、UIKit 把事件解析成 UIEvent + UITouch
3、UIApplication 获取事件,并传递给 UIWindow
4、UIWindow 进行 hit-testing(命中测试)决定哪一个 view 接收事件
5、最终发送给对应的 UIView 的事件方法:touchesBegan、touchesMoved、touchesEnded、touchesCancelled。
命中测试规则:从根视图递归查找 最深层、可交互、可见、clip 内 的 view。
实际用途
1、在 UIView 中接收 UIEvent 的触摸集合
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let event = event else { return }
let all = event.touches(for: self) ?? touches
for t in all {
let loc = t.location(in: self)
let precise = t.preciseLocation(in: self)
}
}
2、处理 motion 事件(摇动)
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake { ... }
}
3、处理外接遥控器事件
override func remoteControlReceived(with event: UIEvent?) { ... }
4、重写UIWindow.sendEvent
class TouchWindow: UIWindow {
override func sendEvent(_ event: UIEvent) {
// 先“旁听”
if event.type == .touches, let touches = event.allTouches {
for t in touches {
switch t.phase {
case .began:
// 触摸开始
case .moved:
// 触摸移动
case .ended:
// 触摸结束
default: break
}
let p = t.location(in: self)
// 使用 t.force / t.timestamp / t.tapCount 等
}
}
// 最后放过事件(交给系统正常分发)
super.sendEvent(event)
}
}
5、iPad 指针事件
使用 hover / scroll 类型的 UIEvent。
注意事项
1、不要在后台线程处理 UIEvent:UIKit 的事件分发在主线程上,所有 UI 更新也必须在主线程。
2、不要保留 UIEvent 进行长期持有:UIEvent 对象在系统内有短期生命周期,保留并在后续异步处理中依赖其内部对象可能出问题;若要保存数据,拷贝需要的值(位置、时间戳等)。
3、性能:sendEvent 每帧调用频率高(特别是滑动/拖拽),避免在 sendEvent 或 touchesMoved 中做重计算或耗时 IO。
4、使用 coalescedTouches 提升绘图平滑度:系统会把高频采样合并成 coalesced 数据,绘图时优先消费这些样本。
5、Pencil 的专用字段:若支持 Apple Pencil,要使用 preciseLocation、altitudeAngle、azimuthAngle 等以获得准确笔迹与压力信息。
6、不同 event.type 读取不同集合:在处理前检查 event.type,不要假设 allTouches 总存在(例如 press 类型事件应该用 allPresses)。
总结
UIEvent 是 UIKit 事件系统里的底层输入载体,存储事件发生时间、类型、子类型、触点等。
使用触摸方法、motion 方法、remote control 方法进行访问。
