目录
- 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
,因此View
的 touchesMoved:withEvent:
在开始一段时间内会持续调用;
当手势识别器成功识别了该滑动手势时,手势识别器的action开始调用,同时通知Application取消View
对事件的响应。之后仅由滑动手势识别器接收事件并响应,View
不再接收事件。
(其实原理都是一样的、只是持续性手势需要一个识别的过程而已)
这里有几个点可以说说:
- 可以看到右侧有一秒的时间差
也就是说View
的后续动作会等待GestureRecognizer
的识别结果。 - 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
可见手势的识别(KTUITapGestureRecognizer
的touchesBeagn
)并不依赖响应链(View
的touchesBeagn
)、而是由[UIWindow sendEvent:]
方法中被UIGestureEnvironment
直接调起。
-
6个子类手势
UIGestureRecognizer为一个抽象基类,定义了实现底层手势识别行为的编程接口,你不应该直接使用他。
- UITapGestureRecognizer:用来识别点击手势,包括单击,双击,甚至三击等。
- UIPinchGestureRecognizer:用来识别手指捏合手势。
- UIPanGestureRecognizer:用来识别拖动手势。
- UISwipeGestureRecognizer:用来识别Swipe手势。
- UIRotationGestureRecognizer:用来识别旋转手势。
- UILongPressGestureRecognizer:用来识别长按手势。
-
"离散手势"和"连续手势"
手势分为离散型手势(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触摸事件全家桶