《Event Handling Guide for iOS》笔记


用户可以以触摸屏幕或摇动设备等多种方式操作他们的iOS设备。 iOS系统会对用户操作设备的时动作做出解释并把该信息传递给应用程序。应用程序对用户各种自然的动作都能处理越全面,用户体验也就越好。
iOS系统通过事件(Event)将用户的操作传递给app,事件分为多种形式:多触摸事件,运动事件以及控制多媒体(通过外部硬件控制)的事件。程序中可以通过内置在UIKit里的常用手势和一些定制化手势来响应用户的各种操作手势。如果可能的话,最好是使用这些内置的手势,可以缩减了代码总量。内置在UIKit里标准控件的手势例如UIButtonUISlider, 分别可以对点击和拖动手势响应。如果想应用程序识别一个独特的手势,比如一个漩涡状运动,可以创建自定义手势。 手势识别器为复杂的事件处理逻辑提供了一个高层次的抽象,应用程序中实现触摸事件处理(touch-event handling)应该考虑使用。

UIKit 内置的手势及对应class包括:

点击Tapping (any number of taps)::UITapGestureRecognizer
向内/外捏Pinching in and out (for zooming a view)::UIPinchGestureRecognizer
滑动/拖动Panning or dragging::UIPanGestureRecognizer
快速滑动Swiping (in any direction)::UISwipeGestureRecognizer
旋转Rotating (fingers moving in opposite directions)::UIRotationGestureRecognizer
长按Long press (also known as “touch and hold”)::UILongPressGestureRecognizer

手势可以分为离散手势和连续手势。 一个离散手势,如一次tap或一次swipe,只发生一次。一个连续手势,如捏合,发生时持续一段时间。 对于离散手势,手势识别就给目标发送一个操作消息。对于连续手势,手势识别则一直发送操作消息给目标直到操作结束。


离散手势与连续手势

你需要做三件事来添加一个内建手势识别到应用程序:
创建并配置一个手势识别实例。该步骤包括分配一个响应手势的目标,操作,以及手势指定的各种属性(比如 numberOfTapsRequired).
把手势识别连接到一个视图。
实现响应手势的操作方法。

对于离散型手势:

- (void)viewDidLoad
 {
     [super viewDidLoad];
 
     // Create and initialize a tap gesture
     UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
          initWithTarget:self action:@selector(respondToTapGesture:)];
 
     // Specify that the gesture must be a single tap
     tapRecognizer.numberOfTapsRequired = 1;
 
     // Add the tap gesture recognizer to the view
     [self.view addGestureRecognizer:tapRecognizer];
 }

// 手势的响应方法
- (IBAction)showGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer 
{
       // Get the location of the gesture
      CGPoint location = [recognizer locationInView:self.view];
 
       // Display an image view at that location
      [self drawImageForGestureRecognizer:recognizer atPoint:location];
 
       // Animate the image view so that it fades out
      [UIView animateWithDuration:0.5 animations:^{
           self.imageView.alpha = 0.0;
      }];
}```
###对于连续型手势:

// Respond to a rotation gesture

  • (IBAction)showGestureForRotationRecognizer:(UIRotationGestureRecognizer *)recognizer
    {
    CGPoint location = [recognizer locationInView:self.view];

     // 随着手势旋转图片
     CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]);
     self.imageView.transform = transform;
    
     [self drawImageForGestureRecognizer:recognizer atPoint:location];
    
    // 通过手势状态决定最终形式
    if (([recognizer state] == UIGestureRecognizerStateEnded) || ([recognizer state] == UIGestureRecognizerStateCancelled)) {
         [UIView animateWithDuration:0.5 animations:^{
              self.imageView.alpha = 0.0;
              self.imageView.transform = CGAffineTransformIdentity;
         }];
    }
    

}

###手势状态
手势识别在有限状态机中的各种预定的状态之间切换。所有的手势识别起点都是“可能的状态UIGestureRecognizerStatePossible”

* 对于离散型手势,手势可能识别失败UIGestureRecognizerStateFailed或被识别UIGestureRecognizerStateRecognized,从而结束识别过程;

![](http://upload-images.jianshu.io/upload_images/3494096-142c1938d62b0fb1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/480)

* 对于连续性手势,每次手势识别改变状态时,除非过渡到失败或取消状态,都给它的响应目标发送消息。当手势第一次被识别时,从可能状态UIGestureRecognizerStatePossible转到开始状态UIGestureRecognizerStateBegan,然后过渡到改变状态UIGestureRecognizerStateChanged, 当手势发生时又从改变状态变为改变状态。 当用户的最后一个手指从视图上抬起时,手势识别过渡到结束状态UIGestureRecognizerStateEnded, 识别完成(结束状态是识别完成状态的一个别名)。如果一个连续手势不再符合某个手势预期的模式时,识别将过渡到取消状态UIGestureRecognizerStateCancelled。

![](http://upload-images.jianshu.io/upload_images/3494096-9a80687726df5df8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520)


###多个手势的竞争处理
使用UIGestureRecognizer类方法,委托方法,以及其子类重写的方法可以处理如两个手势谁先响应、是否同时、哪个手势禁止响应某个动作等竞争问题。最常见的容易冲突的swipe和pan手势,swipe手势会首先被识别为pan手势,但是如何打破这个顺序,让swipe首先被识别,如果确定不是swipe再识别pan?
  • (void)viewDidLoad
    {
    [super viewDidLoad];
    [self.panRecognizer requireGestureRecognizerToFail:self.swipeRecognizer];
    }
requireGestureRecognizerToFail: 使pan手势在等待swipe手势识别过渡到Failed状态期间,始终处于Possible状态。如果swipe手势识别失败了,pan手势分析事件并变为下个状态。如果swipe手势识别过渡到Recognized 或者 Began状态,pan手势就变为Failed 状态。 关于状态过渡的信息参考[“Gesture Recognizers Operate in a Finite State Machine.”](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html#//apple_ref/doc/uid/TP40009541-CH2-SW22)

UIGestureRecognizerDelegate协议提供了方法来阻止手势识别分析触摸。 可以使用协议中的
 gestureRecognizer:shouldReceiveTouch:方法 或  gestureRecognizerShouldBegin: (如果视图或视图控制器不能成为手势识别的代理,可以使用)

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch view] == self.customSubview)
{
// If it is, prevent all of the delegate's gesture recognizers from receiving the touch
return NO;
}
return YES;
}

两个手势识别不能同时识别它们的不同手势,通过代理方法可以让两个手势识别同时识别一个手势gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: 
如果你想要控制两个识别的交互为一个单向关系,你可以重写canPreventGestureRecognizer:方法并返回NO(默认为YES)。 比如,如果你想用一个旋转手势阻止一个捏合手势,但是又不想捏合手势阻止一个旋转手势,可以用 rotationGestureRecognizer canPreventGestureRecognizer:pinchGestureRecognizer;
在iOS 6.0 以后版本中,默认控件操作方法阻止重复手势识别的行为。如一个按钮的默认操作是一个单击。如果你有一个单击手势识别绑定到一个按钮的父视图上,然后用户点击该按钮,最后按钮的操作方法接收触摸事件而不是定制添加的手势识别。 还有:
* 单个手势在 UISlider上的快速滑动,轻扫方向跟slider平行; 
* 单个手指的在 UISwitch 控件上的慢速拖动手势,方向跟switch平行。


###触摸UITouch 和 事件UIEvent
一个触摸UITouch 是一个手指在屏幕上的存在或运动。 一个手势有一个或多个触摸。
一个事件UIEvent包含一个多点触摸序列的所有触摸,包含了应用程序决定如何响应该事件的事件信息。 一个多点触摸序列以一个手指触摸屏幕开始,以最后一个手指离开屏幕结束。 当一个手指移动时,iOS给事件UIEvent发送多个触摸对象UITouch。
每个触摸对象只跟踪一个手指,并且只在触摸序列期间跟踪。 在序列期间,UIKit跟踪手指并更新触摸对象的各种属性。 这些属性包括触摸阶段,它在视图中的位置,前一个位置,以及时间戳。
触摸阶段表明一个触摸何时开始,它是移动的还是静止的,以及它何时结束---当手指不再触摸屏幕的时间。应用程序在任何触摸的每个阶段之间接收事件对象。

![](http://upload-images.jianshu.io/upload_images/3494096-0ed90934e7b493fd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800)
各个阶段对应代理方法:
touchesBegan:withEvent: 当一个或多个手指触摸屏幕时调用
touchesMoved:withEvent:当一个或多个手势移动时调用
touchesEnded:withEvent:当一个或多个手指离开屏幕时调用
touchesCancelled:withEvent:当触摸序列被系统事件取消时调用,比如有一个来电。
注意: 这些方法跟手势识别状态没有关联



当一个触摸发生时,触摸对象从UIApplication对象传递到UIWindow对象。 然后,窗口首先把触摸发送给触摸发生的视图上关联的手势识别器,如果识别出响应手势将响应者变为视图所在vc,而不再发给视图。如果没有识别出手势,才会发送给视图对象自身。即下图123的顺序:

![](http://upload-images.jianshu.io/upload_images/3494096-a5637ceebb8d1521.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520)
我们可以通过属性delaysTouchesBegan等延迟将事件传递给视图,而让手势识别先进行完。
当你通过子类化实现一个定制的手势识别器时,最重要的事情是正确地使用手势识别器的state,在各个状态下获取手势的情况来识别。
- (void)reset; // 在Recognized/Ended, Canceled, 或者Failed状态时复位
- (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;

###响应链
当iOS系统识别到一个事件,它把事件传递给看起来最有可能处理的初始对象,如果初始对象不能处理该事件,iOS继续把它传递给更多的对象,直到找到能够处理该事件的对象。这些有顺序的对象构成一个响应链(responder chain) 。
![响应链](http://upload-images.jianshu.io/upload_images/3494096-87ede27a9574ebe3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600)
响应链与事件的传递链是互逆的:
事件传递过程:
iOS设备的硬件检测到用户的触摸事件 -> 产生UIEvent -> UIApplication -> UIWindow -> ......


![](http://upload-images.jianshu.io/upload_images/3494096-61b5a96cc1aab35b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/840)
iOS系统通过hittest寻找响应者的过程就是一个遍历树的子节点的过程,需要遍历所有相关的vc和vc上面的所有view及其子view,直到最叶子节点的view响应,如果没有子view能响应,就找到最叶的vc;如果没有叶子vc可以响应,就找到UIWindow;否则找到UIApplication。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,848评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,529评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,824评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,329评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,227评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,879评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,218评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,877评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,159评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,155评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,987评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,736评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,273评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,407评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,663评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,158评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,517评论 2 339

推荐阅读更多精彩内容

  • 好奇触摸事件是如何从屏幕转移到APP内的?困惑于Cell怎么突然不能点击了?纠结于如何实现这个奇葩响应需求?亦或是...
    Lotheve阅读 56,395评论 51 597
  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 5,983评论 4 26
  • 手势识别器是附加到视图的对象,将低级别事件处理代码转换为更高级别的操作,它允许视图以控件执行的方式响应操作。 手势...
    坤坤同学阅读 4,027评论 0 9
  • -- iOS事件全面解析 概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实...
    翘楚iOS9阅读 2,935评论 0 13
  • 万物皆为灵,人是灵性的,但又架驭主宰世间所有事物的,这是被我们普遍熟知的。可我们有了灵性,又同样被赋予了欲望,当一...
    离奇之语阅读 488评论 0 1