一个事件总是沿着明确的路径传输直到它被分发给一个对象可以处理它。首先UIApplication
的单例从事件处理队列的头部拿到,并为了处理开始分配。一般,它将事件分配给UIAppldelegate
的keyWindow
对象,由这个对象传递给一个已经初始过的的对象处理它。
触摸事件 对于触摸事件来说,
window
对象首先尝试将事件分发给产生这个事件的视图,这个视图被称为hit-test
view,寻找这个视图的过程就是hit-testing
.动作和远程控制事件 对于这些事件来说,视窗对象通常将这些事件传给第一个响应对象来处理。
Hit-Testing
在父视图之内
Hit-Testing
是一个过程,结果就是返回处理事件的hit-test
view。寻找的顺序就是从这个触摸点所在的最高层的superView
开始,依次遍历其subViews
,看其是否包含这个touch
,不包含则跳过,如果包含,那就遍历当前这个子视图的subViews
, 就这样一直找到包含此touch
的最低级的视图(没有子视图了),那么它就是hit-test
view了。
这是主要使用hitTest:withEvent:
方法返回响应事件的视图,hitTest:withEvent:
在被调用开始时在其方法内部调用pointInside:withEvent:
方法,如果从hitTest:withEvent:
传过来的坐标在这个视图内,那么pointInside:withEvent:
将返回YES
,然后自己的retrun YES
的子视图继续递归调用hitTest:withEvent:
。
如果坐标点不在视图范围内,首次调用pointInside:withEvent:
(也就是最高层父视图调用)就返回NO
,这个点就被忽略,hitTest:withEvent:
则返回nil
。如果父视图的一个子视图返回nil
,那么这个子视图上面所有的视图都会忽略。不在这个子视图上,那么就不会在这个子视图的子视图上。这样就意味着任何视图的点如果在其父视图的外部,那么这些点上的事件就不会被接收。
在父视图之外
在父视图之外的子视图上的事件如果想被接收,需要重写hitTest:withEvent:
方法。 从父视图拿到这个点,将其手动转换到其归属的视图中坐标,然后再执行hit-testing
过程。
|
|
hit-test
View拥有第一优先权响应事件,如果不能响应,那么继续在响应链中分发处理。
响应链
大部分事件都需要依赖响应链来分发。响应链由一系列的响应体组成,从第一个响应体到最后的UIApplication
对象。如果第一个响应体不能响应,那么沿着响应链依次传递下去。
UIResponder
是所有可响应对象的基类,可以响应并处理事件。UIApplication UIViewController UIView
的对象都是响应体,Core Aniamtion Layer
不是响应体。
第一响应体被定位为第一个接受事件,一般来说,第一响应体通常是一个UIView
对象。一个对象成为第一响应体需要实现:
- 重写
canBecomeFirestResponder
方法,并返回YES
- 接收
becomeFirstResponder
消息。如果必要,一个对象可以发送自己的这条消息
第一响应体也要先定义再使用

这两种传递方式都是从一个UIView
对象开始到UIApplication
对象结束,都是视图层级里面由低到高进行传递。区别点就是当一个UIViewController对象A作为childViewController被另外一个UIViewController对象B管理时,如果A中view属性对象不能处理事件时,它会将事件先返回给B,由B中视图继续传递。
当自定义视图去处理事件时,不要直接将事件或者消息通过
nextResponder
在传递链中传递,调用父类的当前处理事件方法实现,让UIKit替你自动处理响应链的遍历。
Motion Events 动作事件
当用户移动、摇晃、倾斜他们设备的时候会产生动作事件,动作事件可以被设备硬件检测到,例如加速计和陀螺仪。
从设备中获取当前方向
如果仅需要获取当前方向而不是方向的确切向量的话可以使用UIDevice
这个类。
|
|
摇晃事件
当用户摇晃设备时,iOS将会计算加速计数据。如果这些数据满足某些条件时,iOS推测为摇晃手势并创建一个UIEvent
对象呈现它。然后iOS将事件传递给当前正使用的App。App可以同时响应摇晃事件和设备方向变化。
Motion Event
比Touch Event
简单,系统会在一个动作事件开始、结束的时候告诉App,但是任何个人动作事件除外。一个动作事件包含事件类型(UIEventTypeMotion
)、事件子类型(UIEventSubtypeMotionShake
)和时间戳。
指明第一响应者
想处理这个事件必须的有一个响应对象作为第一响应者。如何设置第一响应者请参考前面响应链。Motion Event
也是在通过响应链来传递,如果一直传递到window都没有响应的话,并且applicationSupportsShakeToEdit
属性为YES,iOS会显示一个撤销和重做的菜单。
实现动作事件处理方法
动作事件处理方法就3个motionBegan:withEvent
、motionEnded:withEvent
、motionCancelled:withEvent:
。如果想使用动作事件的话,至少得实现motionBegan:withEvent
、motionEnded:withEvent
中的一个方法。
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if motion == .motionShake {
// TODO - 接收到摇晃后逻辑处理
let alert = UIAlertController(title: "Tip", message: "you shake the device!", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
self.navigationController?.present(alert, animated: true, completion: nil)
}
}
// 摇晃时间过长的话就会被取消
override func motionCancelled(_ motion: UIEventSubtype, with event: UIEvent?) {
print("shake motion cancel")
}
设置和请求硬件能力
在info.plist
中设置 加速计accelerometer 陀螺仪gyroscope
- 在
Required device capabilities
对应的数组中添加 - 配置
UIRequiredDeviceCapabilities
key
利用Core Motion捕获设备移动
工作原理
Core Motion
主要获取加速计和陀螺仪的原始数据并将数据传递给app处理。Core Motion
利用独特的运算法则去处理收集到数据,因此可以呈现比较精确的信息。这个处理过程在Core Motion
自己拥有的线程中进行。
Core Motion
与UIKit
是截然不同的。它不关联UIEvent
对象,也不使用响应链。Core Motion
简单直接地将事件分发给需要的app。
Core Motion
事件主要由3部分组成,每一个都包含至少一个单位。
- CMAccelerometerData 捕获每一个空间轴上的加速度
- CMGyroData 捕获x、y、z轴上旋转度
- CMDeviceMotion 封装几个不同的单位 包括高度以及对加速度和旋转度来说更容易使用的单位
CMMotionManager
类是对Core Motion
来说是中心获取点。你可以创建CMMotionManager
的单例对象来明确更新间隔,开始更新请求,处理动作事件。多个CMMotionManager
对象将会影响接收数据的效率。
Core Motion
中所有封装数据的类都是CMLogItem
的子类,这个类里面定义了时间戳,所以动作数据可以根据时间来追踪,并且记录到一个文件里面。一个app可以与前一个事件比较时间戳来更准确地设置更新间隔。
Core Motion
获取装箱动作数据两种方式
- Pull app请求开始更新,稍后定时抽查最近动作事件的单位量
- Push app明确更新间隔 并实现处理数据的block,稍后请求开始更新,并传入
Core Motion
执行队列和处理block。Core Motion
将事件分发给block,在所传的队列中执行。对于大部分app来说,尤其是游戏,推荐使用Pull方式。它通常最有效且代码到最少。Push使用收集数据的app。不管采用哪种方式,当app不再需要时记得关闭,这样可以节省电量。
设置更新间隔
当你利用Core Motion
请求动作事件数据时,你需要明确一个更新间隔。根据自己的需求设置合理的间隔。间隔越长,收集数据的频率越低,消耗的电量越少,间隔越短则相反。
事件频率 | 使用场景 |
---|---|
10 - 20 | 检测设备方向矢量 |
30 - 60 | 利用加速计实现实时用户输入的app和游戏 |
70 - 100 | 检测高频率事件的app 比如:快速点击或摇晃设备 |
10ms等同于100Hz,自己看需求换算一下啦
处理加速度事件
加速计测量的是x、y、z轴上的变化,每一次运动被捕获到CMAccelerometerData
对象里面,封装了类型为CMAcceleration
的结构。
|
|
处理旋转度数据
这里处理跟Accelerometers逻辑一样。