Event Handing guide for iOS


概述:
每当用户操作iOS设备,iOS系统就会解析用户的操作行为,并将这些操作系统封装成事件发送给相应的App,这些事件源包括以下四种:
  • Touch Event
  • Motion Event
  • Remote Control Event
  • Presses Event


目录:



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定义的通用手势识别器:
  • UITapGestureRecognizer(Tapping,any numbers of taps,点击)
  • UIPinchGestureRecognizer(Pinching in and out,for zooming a view,捏)
  • UIPanGestureRecognizer(panning or dragging,拖动)
  • UISwipeGestureRecognizer:(swiping,in any direction,滑动)
  • UIRotationGestureRecognizer(rotating,fingers moving in opposite directions,旋转)
  • UILongPressGestureRecognizer(Long press,also known as “touch and hold”,长按)

Discrete and Continuous Gesture:

手势也分有离散手势与连续手势两种,离散手势如Tap Gesture,只会发送一次Action message。但连续手续,如Pinch Gesture就不一样,它是在手势缩放的整个过程都会连续发送action message。
Responding to Events with Gesture Recognizer:

添加一个手势识别器需要做的三件事:
  1. 创建手势识别器,并绑定Target、Action,或者指定特殊的参数,如numberOfTapsRequired
  2. 将手势识别器添加到View中
  3. 实现手势识别器的Action

响应离散手势与连续手势的区别:
  • 离散手势:只处理一个Action message事件。
  • 连续手势:需要连续处理多个Action message事件。而且在连续手势中还得判断以下几个状态,针对不同的状态进行不同的处理:
    • UIGestureRecognizerStatePossible
    • UIGestureRecognizerStateBegan
    • UIGestureRecognizerStateChanged
    • UIGestureRecognizerStateEnded
    • UIGestureRecognizerStateFailed

所以手势识别的过程实际上就是一个状态机在不同的状态下切换的过程,而且每一次状态的切换都伴随着一个action send to target的过程,如下图所示:


PS:其中Recognized状态已经被End状态取代。
Defining How Gesture Recognizers Interact:

UIView通过一个NSArray属性gestureRecognizers来管理手势识别器,所以一个View也可以同时具有数个手势识别器,可以通过addGestureRecognizer与removeGestureRecognizer来进行添加与移除。

在默认情况下,当一个UIView同时包含若干去手势识别器,这些识别器收到touch的顺序每次都是随机的,但我们可以通过重写或者UIGestureRecognizerDelegate来实现以下的效果:
  1. 制定一个手势识别必须要先执行与另一个手势之前
  2. 允许同时手势进行分析执行
  3. 阻止一个手势识别器去分析touch
通过重写UIGestureRecognizer的子类方法或者实现delegate可以实现以上效果,而且在某些特殊场合十分重要。
如一个界面添加了 swipes and pans手势识别器,这两个手势有比较多的共同点,默认模式容易识别错,所以需要特殊处理。

UIKit中UIControl的子类,不少都是已经封装好特定的手势识别器的了,为了不造成重叠,UIKIT默认若开发者继续添加了相同的手势识别器的,会旁路掉默认的手势识别器。包括以下场景:
  1. 添加Tap到UIButton、UISwitch、UIStepper、UISegmentedControl、UIpageControl
  2. 添加swipe到UISlider
  3. 添加Pan到UISwitch
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属性来改变默认的事件传递规则,如下:
  • delaysTouchesBegin(default:NO):禁止信息传递到view
  • delaysTouchEnded(default:YES):手势识别器延迟结束,等待touch非正式协议完成识别。

手势识别器可以通过调用   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的传递方式略有不同:
  • Touch event:通过hit-test机制,找到hit-testView,若hit-testView无法处理事件,则沿着响应者链条找寻能处理事件的对象。
  • Motion and remote control event:window对象直接把事件传递给第一响应者。
Hit-Testing return the view where a touch occurred:

hit-test机制寻找hit-test view的例子:


- ( UIView *)hitTest:( CGPoint )point withEvent:( UIEvent *)event
hit-test机制的核心方法的默认实现如下所示:
  1. 调用 pointInside:withEvent:判断屏幕点击的点是否在本view范围内,是返回YES,不是返回NO
  2. 若返回YES,则递归subviews,并调用其hit-test方法,若返回NO,则返回nil
  3. 最终有效返回的View就是hit-test view

The responder chain is made up of responder objects

所有继承于UIResponer的对象都可以成为响应者,均具备响应处理事件的能力,而这些对象组成的链条,称为响应者链条。
响应者链条由第一响应者开始,到UIApplication结束。

第一响应者就是首先接收事件的对象,对象可以通过以下方法主动成为第一响应者:
  • 重写canBecomeFirstResponder方法,并返回YES
  • 调用becomFirstResponder方法。


Event不是唯一依赖响应者链条的,响应者链条常用于以下场景:
  • Touch Events:
  • Motion Event:
  • Remote Control event
  • action message
  • editing-meun message
  • text editing

响应者链条的结构:
首先每个事件都会有自己对应的响应者链条,所以响应者链条是动态生成的。
对应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的硬件传感器,如:
  • 加速计:测量设备各个方向上的加速度
  • 陀螺仪:测量设备各个方向上的旋转速率
  • GPS:测量位置

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

能接收远程控制时间,得满足以下条件:
  1. 为各种Action注册handle,MPRemoteCommandCenter 能实现各种事件的注册。
  2. app要处于正在播放的状态。
Handling Remote Control Events

为特定事件添加handle的代码:
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand addTargetUsingBlock:^(MPRemoteCommandEvent *event) {
    // Begin playing the current track.
    [[MyPlayer sharedPlayer] play];
}
Providing Now Playing Information

通过 MPNowPlayingInfoCenter  对象获取播放信息。
Testing Remote Control Events on a Device








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值