事件与手势

Introduction

触摸(Cocoa Touch)就是用户的手指放在屏幕上,系统和硬件联合工作,会知道何时触摸屏幕以及触摸屏幕的未知。触摸事件是在 UIView 上进行的,而不是一个 UIResponder 对象。触摸本身是一个 UITouch 对象,每一个手指即会对应一个 UITouch 对象,多个 UITouch 对象包含在一个 UIEvent 中。系统会将 UIEvent 发送到应用程序中,最后应用程序将 UIEvent 传递给当前一个 UIView。

参考资料

iOS 事件分类

对于 IOS 设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:

1、触屏事件(Touch Event):单点、多点触控以及各种手势操作

2、运动事件(Motion Event):重力、加速度传感器等

3、远端控制事件(Remote-Control Event):远程遥控 iOS 设备多媒体播放等

iOS事件图例

事件封装与响应

UIControl 控件事件分类

参考资料

在控件事件中,简单解释下下面几个事件。

说明:由于是在“iOS 模拟器”中测试的,所以不能用手指,只能用鼠标。

1)UIControlEventTouchDown

指鼠标左键按下(注:只是“按下”)的动作

2)UIControlEventTouchDownRepeat

指鼠标左键连续多次重复按下(注:只是“按下”)的动作,比如,鼠标连续双击、三击、……、多次连击。

说明:多次重复按下时,事件序列是这样的:

UIControlEventTouchDown ->

(UIControlEventTouchUpInside) ->

UIControlEventTouchDown ->

UIControlEventTouchDownRepeat ->

(UIControlEventTouchUpInside) ->

UIControlEventTouchDown ->

UIControlEventTouchDownRepeat ->

(UIControlEventTouchUpInside) ->

...

除了第一次按下外,后面每次摁下都是一个 UIControlEventTouchDown 事件,然后紧跟一个 UIControlEventTouchDownRepeat 事件。

3)UIControlEventTouchDragInside

指按下鼠标,然后在控件边界范围内拖动。

4)UIControlEventTouchDragOutside

与 UIControlEventTouchDragInside 不同的是,拖动时,鼠标位于控件边界范围之外。但首先得有个 UIControlEventTouchDown 事件,然后接一个 UIControlEventTouchDragInside 事件,再接一个 UIControlEventTouchDragExit 事件,这时,鼠标已经位于控件外了,继续拖动就是 UIControlEventTouchDragOutside 事件了。

具体操作是:在控件里面按下鼠标,然后拖动到控件之外。

5)UIControlEventTouchDragEnter

指拖动动作中,从控件边界外到内时产生的事件。

6)UIControlEventTouchDragExit

指拖动动作中,从控件边界内到外时产生的事件。

7)UIControlEventTouchUpInside

指鼠标在控件范围内抬起,前提先得按下,即 UIControlEventTouchDown 或 UIControlEventTouchDownRepeat 事件。

8)UIControlEventTouchUpOutside

指鼠标在控件边界范围外抬起,前提先得按下,然后拖动到控件外,即 UIControlEventTouchDown -> UIControlEventTouchDragInside(n 个) -> UIControlEventTouchDragExit -> UIControlEventTouchDragOutside(n 个) 时间序列,再然后就是抬起鼠标,产生 UIControlEventTouchUpOutside 事件。

自动关联

可以通过 Xib 里面的关联口 IBAction 实现。

手动关联

[button addTarget:self action:@selector(navigatePics:) forControlEvents:UIControlEventTouchUpInside];

手势关联

每一个特定的手势必须关联到 view 对象中才会有作用,一个 view 对象可以关联多个不同的特定手势,但是每一个特定的手势只能与一个 view 相关联。当用户触摸了 view,这个 GestureRecognizer 就会接受到消息,它可以响应特定的触摸事件。

enter description here
enter description here

通俗来说,关联手势与响应分为以下三步:

  • 创建 GestureRecognizer 实例

  • addGestureRecognizer

  • 实现处理手势的方法

UIPanGestureRecognizer

_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlerPanGesture:)];
_panGestureRecognizer.delegate = self;
_panGestureRecognizer.maximumNumberOfTouches = 2;
_panGestureRecognizer.minimumNumberOfTouches = 2;
[self.view addGestureRecognizer:_panGestureRecognizer];
- (void)handlerPanGesture:(UIPanGestureRecognizer *)recognizer
{
if ((recognizer.state == UIGestureRecognizerStateBegan) ||
(recognizer.state == UIGestureRecognizerStateChanged))
{
CGPoint offset = [recognizer translationInView:self.view];
CGRect frame = self.rightViewController.view.frame;
frame.origin.x += offset.x;
if (frame.origin.x >= 0 && frame.origin.x <= kScreenWidth)
{
self.rightViewController.view.frame = frame;
}
[recognizer setTranslation:CGPointZero inView:self.view];
}
else if (recognizer.state == UIGestureRecognizerStateEnded)
{
BOOL isVisible = self.rightViewController.view.frame.origin.x < kScreenWidth / 2;
[self showRightView:isVisible];
}
}

事件分发(Event Delivery)

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个 UIView 对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。

UIWindow 对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。

iOS 系统检测到手指触摸(Touch)操作时会将其打包成一个 UIEvent 对象,并放入当前活动 Application 的事件队列,单例的 UIApplication 会从事件队列中取出触摸事件并传递给单例的 UIWindow 来处理,UIWindow 对象首先会使用 hitTest:withEvent:方法寻找此次 Touch 操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为 hit-test view。

复杂手势控制(Complex Gesture Event)

在 iOS 中一个事件用一个 UIEvent 对象表示,UITouch 用来表示一次对屏幕的操作动作,由多个 UITouch 对象构成了一个 UIEvent 对象。另外,UIResponder 是所有响应者的父类,UIView、UIViewController、UIWindow、UIApplication 都直接或间接的集成了 UIResponder。

Gesture Recognizers(手势识别器)

Gesture Recognizers 是一类手势识别器对象,它可以附属在你指定的 View 上,并且为其设定指定的手势操作,例如是点击、滑动或者是拖拽。当触控事件 发生时,设置了 Gesture Recognizers 的 View 会先通过识别器去拦截触控事件,如果该触控事件是事先为 View 设定的触控监听事件,那么 Gesture Recognizers 将会发送动作消息给目标处理对象,目标处理对象则对这次触控事件进行处理,先看看如下流程图。

Action 传递模型

在 iOS 中,View 就是我们在屏幕上看到的各种 UI 控件,当一个触控事件发生时,Gesture Recognizers 会先获取到指定的事件,然后发送动作消息(action message)给目标对象(target),目标对象就是 ViewController,在 ViewController 中通过事件方法完成对该事件的处理。Gesture Recognizers 能设置诸如单击、滑动、拖拽等事件,通过 Action-Target 这种设计模式,好处是能动态为 View 添加各种事件监听,而不用去实现一个 View 的子类去完成这些功能。

以上过程就是我们在开发中在方法中常见的设置 action 和设置 target,例如为 UIButton 设置监听事件等。

常用手势识别类

在 UIKit 框架中,系统为我们事先定义好了一些常用的手势识别器,包括点击、双指缩放、拖拽、滑动、旋转以及长按。通过这些手势识别器我们可以构造丰富的操作方式。

手势识别类列表

在上表中可以看到,UIKit 框架中已经提供了诸如 UITapGestureRecognizer 在内的六种手势识别器,如果你需要实现自定义的手势识别器,也可以通过继承 UIGestureRecognizer 类并重写其中的方法来完成,这里我们就不详细讨论了。

每一个 Gesture Recognizer 关联一个 View,但是一个 View 可以关联多个 Gesture Recognizer,因为一个 View 可能还能响应多种触控操作方式。当一个触控事件发生时,Gesture Recognizer 接收一个动作消息要先于 View 本身,结果就是 Gesture Recognizer 作为 View 处理触控事件的代表,或者叫代理。当 Gesture Recognizer 接收到指定的事件时,它就会发送一条动作消息(action message)给 ViewController 并处理。

连续和不连续动作

连续与不连续动作

触控动作同时分为连续动作(continuous)和不连续动作(discrete),连续动作例如滑动和拖拽,它会持续一小段时间,而不连续动作例如单击,它瞬间就会完成,在这两类事件的处理上又稍有不同。对于不连续动作,Gesture Recognizer 只会给 ViewContoller 发送一个单一的动作消息(action message),而对于连续动作,Gesture Recognizer 会发送多条动作消息给 ViewController,直到所有的事件都结束。

为一个 View 添加 GestureRecognizer 有两种方式,一种是通过 InterfaceBuilder 实现,另一种就是通过代码实现,我们看看通过代码来如何实现。

Drag(拖拽)

Zoom(缩放)