iOS文档补完计划--UIGestureRecognizer

目录

  • UIGestureRecognizerDelegate
  • 调节手势识别
    • gestureRecognizerShouldBegin:
    • gestureRecognizer:shouldReceiveTouch:
  • 多手势触发
    • gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
    • gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
    • gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:
  • UIGestureRecognizer
    • 手势识别流程
    • 6个子类手势
    • Action
  • 初始化
    • initWithTarget:action:
  • 添加和移除Target&&Action
    • addTarget:action:
    • removeTarget:action
  • 获取手势的触摸和位置
    • locationInView:
    • locationOfTouch:inView:
    • numberOfTouches
  • 获取手势的状态和View
    • state
    • view
    • enabled
  • 取消和延迟触摸
    • cancelsTouchesInView
    • delaysTouchesBegan
    • delaysTouchesEnded
  • 添加依赖
    • requireGestureRecognizerToFail:

UIGestureRecognizerDelegate

你可以通过代理方法、去细致的定制一些识别行为

比如是否触发手势识别、是否进行手势识别。多手势冲突如何处理等

@protocol UIGestureRecognizerDelegate

调节手势识别

  • - gestureRecognizerShouldBegin:

是否继续进行手势识别。默认YES

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;

返回NO则结束识别,不再触发手势。
可以在控件指定的位置开启手势识别

  • - gestureRecognizer:shouldReceiveTouch:

window对象在有触摸事件发生时。默认YES

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
       shouldReceiveTouch:(UITouch *)touch;

位于touchesBegan:withEvent:之前被调用。
如果返回NO、该事件将不会被通知给GestureRecognizer


多手势触发

  • - gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:

是否支持多手势触发。默认NO。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer

正常情况下只会有一个识别器进行手势识别、也就是上层对象识别后则不再继续传播。
如果返回YES、响应者链上层对象触发手势识别后、如果下层对象也添加了手势并成功识别也会继续执行。

  • gestureRecognizer:shouldRequireFailureOfGestureRecognizer:

这个方法返回YES,第一个则失效

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

顾名思义吧、Failure Of GestureRecognizer

  • - gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:

这个方法返回YES,第二个手势则失败

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

顾名思义吧、Fail By GestureRecognizer

需要注意这里

些方法都有两个UIGestureRecognizer参数、所以在一个对象的代理中返回并不一定能起到决定性作用
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:举例:
只要任意一个返回YES、则这两个就可以同时识别;
只有两个都返回NO的时候、才是互斥的。


UIGestureRecognizer

  • 手势识别流程

大致理解是,Window在将事件传递给hit-tested view之前,会先将事件传递给相关的手势识别器并由手势识别器优先识别。若手势识别器成功识别了事件,就会取消hit-tested view对事件的响应;若手势识别器没能识别事件,hit-tested view才完全接手事件的响应权。

佐证的话、你可以自定义一个子类并且重载一些方法、这里直接贴结果
先用一个离散型手势做实验:

14:20:17-[KTUITapGestureRecognizer touchesBegan:withEvent:]
14:20:17-[KTUITapGestureRecognizer2 touchesBegan:withEvent:]
14:20:17-[View2 touchesBegan:withEvent:]
14:20:17-[View touchesBegan:withEvent:]
14:20:18-[KTUITapGestureRecognizer touchesEnded:withEvent:]
14:20:18-[KTUITapGestureRecognizer2 touchesEnded:withEvent:]
14:20:18-[View2 tapAction]
14:20:18-[View2 touchesCancelled:withEvent:]
14:20:18-[View touchesCancelled:withEvent:]
而对于持续型手势

在一开始滑动的过程中,手势识别器处在识别手势阶段,滑动产生的连续事件既会传递给手势识别器又会传递给View,因此ViewtouchesMoved:withEvent:在开始一段时间内会持续调用;
当手势识别器成功识别了该滑动手势时,手势识别器的action开始调用,同时通知Application取消View对事件的响应。之后仅由滑动手势识别器接收事件并响应,View不再接收事件。
(其实原理都是一样的、只是持续性手势需要一个识别的过程而已)

这里有几个点可以说说:

  1. 可以看到右侧有一秒的时间差
    也就是说View的后续动作会等待GestureRecognizer的识别结果。
  2. KTUITapGestureRecognizer以及KTUITapGestureRecognizer2的方法都被触发了
    也就是说是hit-tested列表中所有View上的手势识别器都会得到机会去识别事件。(依赖UITouch中的gestureRecognizers属性)
    至于最后触发谁、取决于代理中的设置。默认按照响应链的顺序。

并且手势在触摸事件处理流程中,处于观察者的角色,其不是view层级结构的一部分,所以也不参与或者依赖responder chain(你把上层View的touch不调用super也影响不了)。

其流程大概如下图所示:

注:图中view与手势的关系是,手势关联在view或view的superview(可能多级)上。

  • 手势的识别并不依赖响应链
  • [KTUITapGestureRecognizer touchesBegan:withEvent:]堆栈信息:
-[KTUITapGestureRecognizer touchesBegan:withEvent:](self=0x00006000003d6700, _cmd="touchesBegan:withEvent:", 0x0000600000dd1d40) ITapGestureRecognizer.m:14
-[UIGestureRecognizer _touchesBegan:withEvent:] + 240
-[UITouchesEvent _sendEventToGestureRecognizer:] + 287
-[UIGestureEnvironment _updateForEvent:window:]_block_invoke + 64
-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 276
-[UIGestureEnvironment _updateForEvent:window:] + 200
-[UIWindow sendEvent:] + 4058
-[UIApplication sendEvent:] + 352

可见手势的识别(KTUITapGestureRecognizertouchesBeagn)并不依赖响应链(ViewtouchesBeagn)、而是由[UIWindow sendEvent:]方法中被UIGestureEnvironment直接调起。

  • 6个子类手势

UIGestureRecognizer为一个抽象基类,定义了实现底层手势识别行为的编程接口,你不应该直接使用他。

  • "离散手势"和"连续手势"

手势分为离散型手势(discrete gestures)和持续型手势(continuous gesture)

系统提供的离散型手势包括点按手势(UITapGestureRecognizer)和轻扫手势(UISwipeGestureRecognizer),其余均为持续型手势。

两者主要区别在于状态变化过程:

离散型:
识别成功:Possible —> Recognized
识别失败:Possible —> Failed

持续型:
完整识别:Possible —> Began —> [Changed] —> Ended
不完整识别:Possible —> Began —> [Changed] —> Cancel

  • Action

最多含有1个参数、这与UIControl不同

- (void)handleGesture;
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;

你可以向gestureRecognizer询问一些东西、比如可以通过调用locationInView:locationOfTouch:inView:来询问手势的位置。


初始化

  • - initWithTarget:action:

通过target&&action的形式初始化一个手势识别器

- (instancetype)initWithTarget:(id)target 
                        action:(SEL)action;

添加和移除Target&&Action

  • - addTarget:action:

添加一对Target&&Action

- (void)addTarget:(id)target 
           action:(SEL)action;

与UIControl的机制一样、Target&&Action成对作为标识、再次添加无效。

  • - removeTarget:action

移除一对Target&&Action

- (void)removeTarget:(id)target 
              action:(SEL)action;

传递nil则匹配所有动作:
Target=nil则删除所有
Action=nil则删除该Target所有


获取手势的触摸和位置

  • - locationInView:

获取手势触摸的位置

- (CGPoint)locationInView:(UIView *)view;

如果传入nil则指定为window

比如我们可以设定允许判定手势的rect

//设置点击的范围
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
//获取当前的触摸点
  CGPoint curp = [touch locationInView:self.imageView];
  if (curp.x <= self.imageView.bounds.size.width*0.5) {
      return NO;
  }else{

      return YES;
  }
}
  • - locationOfTouch:inView:

多点触摸时、查找指定touch的poinit

- (CGPoint)locationOfTouch:(NSUInteger)touchIndex 
                    inView:(UIView *)view;

touchIndex代表指定touch的索引
view传入nil则指定为window

  • numberOfTouches

该手势所获取到的总触摸点数

@property(nonatomic, readonly) NSUInteger numberOfTouches;

获取手势的状态和View

  • state

手势识别器当前的识别状态

@property(nonatomic, readwrite) UIGestureRecognizerState state;

UIGestureRecognizerState是一个枚举类型

typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    //尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态
    UIGestureRecognizerStatePossible,   
    //手势已经开始,此时已经被识别,但是这个过程中可能发生变化,手势操作尚未完成
    UIGestureRecognizerStateBegan,     
    //手势状态发生改变
    UIGestureRecognizerStateChanged, 
    // 手势识别操作完成(此时已经松开手指)  
    UIGestureRecognizerStateEnded, 
    //手势被取消,恢复到默认状态   
    UIGestureRecognizerStateCancelled, 
    //手势识别失败,恢复到默认状态
    UIGestureRecognizerStateFailed,    
    //手势识别完成,同end
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded 
};

手势分为离散型手势(discrete gestures)和持续型手势(continuous gesture)
二者可能经历的状态不一样。

具体可以返回去看看《"离散手势"和"连续手势"》那一块的说明。

  • view

手势所添加到的视图

@property(nonatomic, readonly) UIView *view;
  • enabled

手势识别器是否开启。默认YES

@property(nonatomic, getter=isEnabled) BOOL enabled;

如果在手势识别器正在识别手势时将此属性更改为NO,则手势识别器将转换为已取消状态。


取消和延迟触摸

  • cancelsTouchesInView

识别成功后--是否向View发送cancel消息。默认YES

@property(nonatomic) BOOL cancelsTouchesInView;

若设置成NO,表示手势识别成功后不取消响应链对事件的响应,事件依旧会传递给hit-test view。

  • delaysTouchesBegan

手势识别器在识别手势期间,是否截断事件,即不会将事件发送给hit-tested view。默认为NO。

@property(nonatomic) BOOL delaysTouchesBegan;

正常情况

14:20:17-[KTUITapGestureRecognizer touchesBegan:withEvent:]
14:20:17-[KTUITapGestureRecognizer2 touchesBegan:withEvent:]
14:20:17-[View2 touchesBegan:withEvent:]
14:20:17-[View touchesBegan:withEvent:]
14:20:18-[KTUITapGestureRecognizer touchesEnded:withEvent:]
14:20:18-[KTUITapGestureRecognizer2 touchesEnded:withEvent:]
14:20:18-[View2 tapAction]
14:20:18-[View2 touchesCancelled:withEvent:]
14:20:18-[View touchesCancelled:withEvent:]

任意VIewtap.delaysTouchesBegan = YES;之后

14:55:37-[KTUITapGestureRecognizer touchesBegan:withEvent:]
14:55:37-[KTUITapGestureRecognizer2 touchesBegan:withEvent:]
14:55:37-[KTUITapGestureRecognizer touchesEnded:withEvent:]
14:55:37-[KTUITapGestureRecognizer2 touchesEnded:withEvent:]
14:55:37-[View2 tapAction]

可以看出来多个手势只要有一个设置为YES、整条响应链上的VIew都不会收到消息。

  • delaysTouchesEnded

当手势识别失败时,若此时触摸已经结束,是否延迟调用响应者的 touchesEnded:withEvent。默认YES

@property(nonatomic) BOOL delaysTouchesEnded;

若设置成NO,则在手势识别失败时会立即通知Application发送状态为end的touch事件给hit-tested view以调用 touchesEnded:withEvent: 结束事件响应。
若设置成YES,会延迟大概0.15ms,期间没有接收到别的touch才会发送touchesEnded。


添加依赖

  • requireGestureRecognizerToFail:

只有当另一个手势识别失败时才会除非本手势

- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;

[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];
这样即使singleTapGesture已经识别成功、也会等到doubleTapGesture识别失败再触发自身Action


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果愿意补充以及不吝赐教小弟会更加感激。


参考资料

官方文档-UIGestureRecognizer
你真的了解UIGestureRecognizer吗?
iOS-UIGestureRecognizer详解-原理篇
UIGestureRecognizer学习笔记
iOS触摸事件全家桶

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341