概述:
每当用户操作iOS设备,iOS系统就会解析用户的操作行为,并将这些操作系统封装成事件发送给相应的App,这些事件源包括以下四种:
- Touch Event
- Motion Event
- Remote Control Event
- Presses Event
目录:
- Gesture Recognizers
- Event Delivery: The Responder Chain
- Multitouch Events
- Motion Events
- Remote Control Events
UIKit Makes it Easy for Your App to Detect Gestures:
原本手指在屏幕的触控事件全都是通过UIView的范畴(非正式协议)事件来监听:
- (
void
)touchesBegan:(
NSSet
<
UITouch
*> *)touches withEvent:(
UIEvent
*)event
- (
void
)touchesEnded:(
NSSet
<
UITouch
*> *)touches withEvent:(
UIEvent
*)event
- (
void
)touchesMoved:(
NSSet
<
UITouch
*> *)touches withEvent:(
UIEvent
*)event
- (
void
)touchesCancelled:(
NSSet
<
UITouch
*> *)touches withEvent:(
UIEvent
*)event
识别成功会调用END,识别失败调用cancel,如在view上还有手势识别器的情况下,当手势识别器识别成功,touch非正式协议就会调用cacenl。
但UIKIT直接将再进行封装成各种UIGestureRecognizer用于识别一些通用的手势,甚至直接将手势应用于UIControl的子类,如UIButton、UISlider等,让其能直接相应与自身图形相关的触摸事件。这些手势识别器都是利用target-Action实现的,均将手势与事件直接一一对应。当然我们也可以自己定义自己所需的手势。
Target-Action机制与响应链机制的关系:
Target-Action,其实就是send action message to target when action happen,所以就是一种将事件与响应对象一一绑定的机制。而手势识别器就是应用了该机制,所以在初始化的时候会要求参入target与SEL两个关键参数以实现绑定,达到若某事件发生,就可以调用target-SEL实现效果。
而响应链机制就是iOS中事件传递的机制,它与Target-Action的关系是,当Target-Action中的SEL为nil,也就是对象无法处理事件时,事件就会沿着响应者链条回传。
|
Built-in Gesture Recognizers common Gestures:
UIKit定义的通用手势识别器:
|
Discrete and Continuous Gesture:
手势也分有离散手势与连续手势两种,离散手势如Tap Gesture,只会发送一次Action message。但连续手续,如Pinch Gesture就不一样,它是在手势缩放的整个过程都会连续发送action message。
|
Responding to Events with Gesture Recognizer:
添加一个手势识别器需要做的三件事:
响应离散手势与连续手势的区别:
所以手势识别的过程实际上就是一个状态机在不同的状态下切换的过程,而且每一次状态的切换都伴随着一个action send to target的过程,如下图所示:
PS:其中Recognized状态已经被End状态取代。
|
Defining How Gesture Recognizers Interact:
UIView通过一个NSArray属性gestureRecognizers来管理手势识别器,所以一个View也可以同时具有数个手势识别器,可以通过addGestureRecognizer与removeGestureRecognizer来进行添加与移除。
在默认情况下,当一个UIView同时包含若干去手势识别器,这些识别器收到touch的顺序每次都是随机的,但我们可以通过重写或者UIGestureRecognizerDelegate来实现以下的效果:
通过重写UIGestureRecognizer的子类方法或者实现delegate可以实现以上效果,而且在某些特殊场合十分重要。
如一个界面添加了
swipes and pans手势识别器,这两个手势有比较多的共同点,默认模式容易识别错,所以需要特殊处理。
UIKit中UIControl的子类,不少都是已经封装好特定的手势识别器的了,为了不造成重叠,UIKIT默认若开发者继续添加了相同的手势识别器的,会旁路掉默认的手势识别器。包括以下场景:
|
Gesture Recognizer Interpret Raw Touch Events
手势识别器的原理本质上就是通过UIView通过范畴实现的非正式协议方法来实现的,如下图:
通过重写以上方法即可监听用户在View的触摸行为,其中有两个很重要的参数,UITouch与UIEvent,当iOS在有交互时间产生,UIEvent就会在响应链中传递,
而UIEvent有有两个重要枚举
UIEventType、
UIEventSubtype。
其中
UIEventType的结构如下:
typedef
NS_ENUM
(NSInteger, UIEventType) {
UIEventTypeTouches, UIEventTypeMotion, UIEventTypeRemoteControl, UIEventTypePresses NS_ENUM_AVAILABLE_IOS ( 9 _0),
};
UIEventSubtype的结构如下:
typedef
NS_ENUM
(NSInteger, UIEventSubtype) {
// available in iPhone OS 3.0 UIEventSubtypeNone = 0 , // for UIEventTypeMotion, available in iPhone OS 3.0 UIEventSubtypeMotionShake = 1 , // for UIEventTypeRemoteControl, available in iOS 4.0 UIEventSubtypeRemoteControlPlay = 100 , UIEventSubtypeRemoteControlPause = 101 , UIEventSubtypeRemoteControlStop = 102 , UIEventSubtypeRemoteControlTogglePlayPause = 103 , UIEventSubtypeRemoteControlNextTrack = 104 , UIEventSubtypeRemoteControlPreviousTrack = 105 , UIEventSubtypeRemoteControlBeginSeekingBackward = 106 , UIEventSubtypeRemoteControlEndSeekingBackward = 107 , UIEventSubtypeRemoteControlBeginSeekingForward = 108 , UIEventSubtypeRemoteControlEndSeekingForward = 109 ,
};
从上面的数据结构可以,其实UITouch是封装在UIEvent中的,但这里非正式协议中的方法为了开发者方便把它单独抽离出来成为一个参数。
|
Regulating the Delivery of Touch to Views
默认的touch event时间传递的顺序如上图所示,很明显每个action是先经过Gesture recognize再到View的。若Gesture recognize先于View完成手势识别,那么view中的touch event分析就会进入cancel状态。
可以通过设置UIGestureRecognizer的delaysTouchesBegin、delaysTouchEnded属性来改变默认的事件传递规则,如下:
手势识别器可以通过调用
ignoreTouch:forEvent:来忽略特殊的事件。
|
Creating a Custom Gesture Recognizer
可以通过继承UIGestureRecognizer并重写以下基础方法,即可实现自定义的手势识别器:
- (void)reset;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
|
当用户因操作而产生事件时,UIKIT就会创建一个UIEvent包含处理事件所需的信息,然后系统把这个事件放置到当前激活的App的时间队列里面。然后处于激活状态的UIApplication对象从任务队列中获取UIEvent并然后将其传递到key window,然后通过响应链找到能处理该事件的对象。
对于不同的UIEvent的传递方式略有不同:
|
Hit-Testing return the view where a touch occurred:
hit-test机制寻找hit-test view的例子:
- (
UIView
*)hitTest:(
CGPoint
)point withEvent:(
UIEvent
*)event
hit-test机制的核心方法的默认实现如下所示:
|
The responder chain is made up of responder objects
所有继承于UIResponer的对象都可以成为响应者,均具备响应处理事件的能力,而这些对象组成的链条,称为响应者链条。
响应者链条由第一响应者开始,到UIApplication结束。
第一响应者就是首先接收事件的对象,对象可以通过以下方法主动成为第一响应者:
Event不是唯一依赖响应者链条的,响应者链条常用于以下场景:
响应者链条的结构:
首先每个事件都会有自己对应的响应者链条,所以响应者链条是动态生成的。
对应Touch Event:由hit-test机制生成。
对于Motion、Remote Event:由程序设定,设定谁了first responder,然后若first responder无法响应,然后就是按照响应者链条回传,就是first responder——》。。。——》application的过程,中间的传递规律如下图。
|
The responder chain follows a specific Delivery
响应者链条是专门用于传递用户与设备交互而产生的事件,而别的系统事件是通过的别的方式来传递的,这里得区分。
响应链的传递规则图:
|
Creating a SubClass of UIResponder
|
implementing the Touch-event Handing method in your subclass
|
Tracking the phase and location of a touch event
|
Retrieving and Querying Touch Objects
|
Handling Tap Gestures
|
Handling Swipe and Drag Gestures
|
Handling a Complex Multitouch Sequence
|
Specifying Custom Touch Event Behavior
|
Intercepting Touches by Overriding Hit-Testing
|
Forwarding Touch Events
|
Best Practices for Handling Multitouch Events
|
当用设备移动、摇晃、倾斜,就会生成motion events事件。这类型的事件大多产生于iOS的硬件传感器,如:
|
Getting the Current Device Orientation with UIDevice
若想获取设备的方向,可以直接通过UIDevice实现。实例代码如下:
-(void) viewDidLoad {
// Request to turn on accelerometer and begin receiving accelerometer events
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)orientationChanged:(NSNotification *)notification {
// Respond to changes in device orientation
}
-(void) viewDidDisappear {
// Request to stop receiving accelerometer events and turn off accelerometer
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
}
PS:就是通过通知的方式获取设备的方向。
|
Detecting Shake-Motion Events with UIEvent
由于motion都是直接发给第一响应者,若第一响应者无法处理,则继续在响应者链条回传。
若设置了第一响应者,还得实现以下方法才能完成得到相应的数据,实现功能。
- (
void
)motionBegan:(
UIEventSubtype
)motion withEvent:(
UIEvent
*)event
- (
void
)motionEnded:(
UIEventSubtype
)motion withEvent:(
UIEvent
*)event
|
Setting and Checking Required Hardware Capabilities for Motion Events
略。
|
Capturing Device Movement with Core Motion
捕抓motion的核心数据的教程。
|
|
能实现用外部设备控制多媒体APP,又或者在锁屏的情况下还能控制多媒体。使用命令模式实现。
|
Preparing Your App for Remote Control Events
能接收远程控制时间,得满足以下条件:
|
Handling Remote Control Events
为特定事件添加handle的代码:
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand addTargetUsingBlock:^(MPRemoteCommandEvent *event) {
// Begin playing the current track.
[[MyPlayer sharedPlayer] play];
}
|
Testing Remote Control Events on a Device
|