前言
在事件(如触摸屏幕)产生后,系统是如何通知到你的 App,在 App 内部是如何进行传递,最终又是如何确定最终的响应者的。
这些肯定是有规则的,在 App 内部,一个事件会按照一个规则(视图层级关系)去遍历寻找这个事件的最佳响应者,但是这个响应者有可能不处理事件,那么它又需要沿着一定的规则(响应者链)去传递这个事件,如果最终都无人处理,那么将这个事件抛弃,也就是不处理。
事件类型
先来看看什么是事件。
事件对应的对象为 UIEvent
,它有一个属性为 type,是 EventType 类型,EventType 是一个枚举类型:
public enum EventType : Int {
case touches // 触摸事件
case motion // 运动事件
case remoteControl // 远程控制事件
@available(iOS 9.0, *)
case presses // 按压事件
}
所以 iOS 中的事件有四种:
-
touch events
(触摸事件) -
motion events
(运动事件) -
remote-control events
(远程控制事件) -
press events
(按压事件)
1.触摸事件
触摸事件就是我们的手指
或者苹果的 Pencil(触笔)
在屏幕中所引发的互动,比如轻点、长按、滑动等操作,是我们最常接触到的事件类型。触摸事件对象可以包含一个或多个触摸
,并且每个触摸由 UITouch 对象表示。当触摸事件发生时,系统会将其沿着线路传递,找到适当的响应者并调用适当的方法,例如 touchedBegan:withEvent:
。响应者对象会根据触摸来确定适当的方法。
触摸事件分为以下几类:
- 手势事件 :
- 长按手势 (
UILongPressGestureRecognizer
) - 拖动手势 (UIPanGestureRecognizer)
- 捏合手势 (UIPinchGestureRecognizer)
- 响应屏幕边缘手势 (UIScreenEdgePanGestureRecognizer)
- 轻扫手势 (UISwipeGestureRecognizer)
- 旋转手势 (UIRotationGestureRecognizer)
- 点击手势 (UITapGestureRecognizer)
- 长按手势 (
- 自定义手势
- 点击
button
相关
触摸事件对应的对象为 UITouch
。
2.运动事件
iPhone 内置陀螺仪、加速器和磁力仪,可以感知手机的运动情况。iOS 提供了 Core Motion
框架来处理这些运动事件。根据这些内置硬件,运动事件大致分为三类:
-
陀螺仪相关
:陀螺仪会测量设备绕X-Y-Z
轴的自转速率、倾斜角度等。通过Core Motion
提供的一些 API 可以获取到这些数据,并进行处理;通过系统可以通过内置陀螺仪获取设备的朝向,以此对 App UI 做出调整 -
加速器相关
:设备可以通过内置加速器测量设备在X-Y-Z
轴速度的改变;Core Motion
提供了高度计(CMAltimeter
)、计步器(CMPedometer
)等对象,来获取处理这些产生的数据 -
磁力仪相关
:使用磁力仪可以获取当前设备的磁极、方向、经纬度等数据,这些数据多用于地图导航开发
不过官方文档中指出,这些都是属于Core Motion 库框架
,Core Motion 库中的事件直接由 Core Motion 内部进行处理
,不会通过响应者链,所以UIKit 框架能接收的事件暂时只包括摇一摇
(EventSubtype.motionShake)。
3.远程控制事件
远程控制事件允许响应者对象从外部附件或耳机接受命令
,以便它可以管理音频和视频。目前 iOS 仅提供我们远程控制音频和视频的权限,即对音频实现暂停/播放、上一曲/下一曲、快进/快退操作。以下是它能识别的类型:
public enum EventSubtype : Int {
case remoteControlPlay
case remoteControlPause
case remoteControlStop
case remoteControlTogglePlayPause
case remoteControlNextTrack
case remoteControlPreviousTrack
case remoteControlBeginSeekingBackward
case remoteControlEndSeekingBackward
case remoteControlBeginSeekingForward
case remoteControlEndSeekingForward
}
4.按压事件
iOS 9.0 之后提供了 3D Touch 事件,通过使用这个功能可以做如下操作:
-
Quick Actions
:重压 App icon 可以进行很多快捷操作 -
Peek and Pop
:使用这个功能对文件进行预览和其他操作,可以在手机自带 “信息” 里面试验 -
Pressure Sensitivity
:压力响应敏感,可以在备忘录中选择画笔,按压不同力度画出来的颜色深浅不一样
事件传递到 App 之前
我们一般说的事件传递的起点在于UIApplication
所管理的事件队列中开始分发
的时候,但事件真正的起点
在于你手指触摸到屏幕
的那一刻开始(以触摸事件为例),那么在触摸屏幕到事件队列开始分发发生了什么?我们就以一个触摸事件来说明这个过程。
- 当你通过一个动作(触摸/摇晃/线控)等触发一个事件,这时候会唤起处于休眠状态的 cup
- 事件会通过使用 IOKit.framework 来封装成 IOHIDEvent 对象。
IOKit.framework
是一个系统框架的集合,用来驱动一些系统事件。IOHIDEvent
中的 HID 代表 Human Interface Device,即人机交互驱动
。
- 系统通过
mach port
(IPC 进程间通信) 将IOHIDEvent
对象转发给SpringBoard.app
。
SpringBoard.app
是 iOS系统桌面 App
,用来管理 iOS 的主屏幕,除此之外像WindowServer(窗口服务器)
、bootstrapping(引导应用程序)
,以及在启动时候系统的一些初始化设置都是由这个特定的应用程序负责的。它是我们 iOS 程序中,事件的第一个接收者。它只能接受少数的事件,比如:按键(锁屏/静音等)、触摸、加速、接近传感器等几种 Event,随后使用mach port 转发给需要的 App 进程
-
SpringBoard.app
会找到可以响应这个事件的 App
,并通过mach port
(IPC 进程间通信) 将 IOHIDEvent 对象转发给当前 App 的主线程
。 -
前台 App 主线程
Runloop
接收到SpringBoard.app
转发过来的消息之后,触发对应的mach port
的Source1
回调__IOHIDEventSystemClientQueueCallback()
-
Source1
回调内部触发了Source0
回调__UIApplicationHandleEventQueue()
7.Source0
回掉内部,将 IOHIDEvent 对象转化为 UIEvent
-
Soucre0
回调内部调用UIApplication
的+[sendEvent:]
方法,将UIEvent
传给UIWindow
UIWindow
接收到这个事件后, 开始传递事件
UIApplication
管理了一个事件队列
,之所以是队列而不是栈,是因为队列的特点是先进先出,先产生的事件先处理。UIApplication
会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow
),主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,
网友评论