https://juejin.cn/post/6894518925514997767
当用户触摸了屏幕。UIKit 为我们提供了命中测试(hit-testing)来确定触摸事件的响应者
在 检查自身可否接收事件 中,如果视图符合以下三个条件中的任一个,都会无法接收事件:
view.isUserInteractionEnabled = false
view.alpha <= 0.01
view.isHidden = true
检查坐标是否在自身内部 这个过程使用了 func point(inside point: CGPoint, with event: UIEvent?) -> Bool方法来判断坐标是否在自身内部,该方法是可以被重写的。
从后往前遍历子视图重复执行 指的是按照 FILO 的原则,将其所有子视图按照「后添加的先遍历」的规则进行命中测试。该规则保证了系统会优先测试视图层级树中最后添加的视图,如果视图之间有重叠,该视图也是同级视图中展示最完整的视图,即用户最可能想要点的那个视图。
在 按顺序看看平级的兄弟视图 时,若发现已经没有未检查过的视图了,则应走向 诶?没有子视图符合要求?。
在找到了第一响应者之后,整个响应链也随着确定下来了。所谓响应链是由响应者组成的一个链表,链表的头是第一响应者,链表的每个结点的下一个结点都是该结点的 next 属性。
其实响应链就是在命中测试中,走通的路径。
默认来说,若该结点是 UIView 类型的话,这个 next 属性是该结点的父视图。但也有几个例外:
如果是 UIViewController 的根视图,则下一个响应者是 UIViewController。
如果是 UIViewController
如果 UIViewController 的视图是 UIWindow 的根视图,则下一个响应者是 UIWindow 对象。
如果 UIViewController 是由另一个 UIViewController 呈现的,则下一个响应者是第二个 UIViewController。
UIWindow的下一个响应者是 UIApplication。
UIApplication 的下一个响应者是 app delegate。但仅当该 app delegate 是 UIResponder 的实例且不是 UIView、UIViewController 或 app 对象本身时,才是下一个响应者。
下面举个例子来说明。如下图所示,触摸点是🌟,那根据命中测试,B 就成为了第一响应者。由于 C 是 B 的父视图、A 是 C 的父视图、同时 A 是 Controller 的根视图,那么按照规则,响应链就是这样的:
视图 B -> 视图 C -> 根视图 A -> UIViewController 对象 -> UIWindow 对象 -> UIApplication 对象 -> App Delegate
图中浅灰色的箭头是指将 UIView 直接添加到 UIWindow 上情况。
沿响应链传递事件
触摸事件首先将会由第一响应者响应,触发其 open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 等方法,根据触摸的方式不同(如拖动,双指),具体的方法和过程也不一样。若第一响应者在这个方法中不处理这个事件,则会传递给响应链中的下一个响应者触发该方法处理,若下一个也不处理,则以此类推传递下去。若到最后还没有人响应,则会被丢弃(比如一个误触)。
一般UIView默认沿着响应链self.nextResponder传递,UIControl会截断处理
比如上图情形,如果A、B、C都是UIView,则均会触发touchBegin等一系列事件
如果把中间的C换成UIControl,则只有最上层的B以及中间的C能够触发,A touchBegin不会被调用
当手势识别参与响应链
除了事件沿响应链传递的流程同时一起发生的还有图中下半部分手势识别的部分。
从图中我们可以看到,在通过命中测试找到第一响应者之后,会将 UITouch 分发给 UIResponder 的 touches 系列方法,同时也会分发给手势识别系统,且分发给手势识别系统的时间会比分发给UIResponder的时间要早,让这两个处理系统同时工作。
首先要注意的是,上图中蓝色部分的流程并不会只执行一次,举例来说:当我们用一根手指在一个视图上缓慢滑动时,会产生一个 UITouch 对象,这个 UITouch 对象会随着你手指的滑动,不断的更新自身,同时也不断地触发 touches 系列方法。一般来说,我们会得到如下类似的触发顺序:
touchesBegan // 手指触摸屏幕
touchesMoved // 手指在屏幕上移动
touchesMoved // ...
touchesMoved // ...
touchesMoved // 手指在屏幕上移动
touchesEnded // 手指离开屏幕
UITouch 的 gestureRecognizers 属性中的存储了在寻找第一响应者的过程中收集到的手势,而在不断触发 touches 系列方法的过程中,手势识别系统也在在不停的判断当前这个 UITouch 是否符合收集到的某个手势。
当手势识别成功: 被触摸的那个视图,也就是第一响应者会收到 touchesCancelled 的消息,并且该视图不会再收到来自该 UITouch 的 touches 事件。同时也让该 UITouch 关联的其他手势也收到 touchesCancelled,并且之后不再收到此 UITouch 的 touches 事件。这样做就实现了该识别到的手势能够独占该 UITouch。具体表现参考如下:
touchesBegan // 手指触摸屏幕
touchesMoved // 手指在屏幕上移动
touchesMoved // ...
touchesMoved // ...
touchesMoved // 手指在屏幕上移动
touchesCancelled // 手势识别成功,touches 系列方法被阻断
// 现在手指💅并没有离开屏幕
// 但如果继续滑动🛹的话
// 并不会触发 touches 系列方法
当手势识别未成功: 指暂时未识别出来,不代表以后不会识别成功,不会阻断响应链。注意这里指的是未成功,并不一定是失败。在手势的内部状态中,手势大部分情况下状态是 .possible,指的是 UITouch 暂时与其不匹配,但之后可能有机会识别成功。而 .fail 是真的识别失败,指的是以目前的触摸情况来看已经不可能是这个手势了,并且在下个 runloop 会从 gestureRecognizers 中移除该手势。
下面看几个例子
父view上加了一个UIButton,UIView,UICollectionView,父View上增加点击手势
代码如下
AView *av = [[AView alloc] initWithFrame:CGRectMake(0, 100, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-100)];
av.backgroundColor = [UIColor lightGrayColor];
CustomTapGestureRecognizer *tap = [[CustomTapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[av addGestureRecognizer:tap];
tap.delegate = self;
[self.view addSubview:av];
BButton *bb = [BButton buttonWithType:UIButtonTypeCustom];
bb.frame = CGRectMake(0, 0, 100, 100);
[bb addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
bb.backgroundColor = [UIColor redColor];
[av addSubview:bb];
BView *bv = [[BView alloc] initWithFrame:CGRectMake(120, 0, 150, 150)];
bv.backgroundColor = [UIColor cyanColor];
[av addSubview:bv];
self.collectionView.frame = CGRectMake(0, 300, 300, 300);
[av addSubview:self.collectionView];
[self.collectionView registerClass:[CustomCell class] forCellWithReuseIdentifier:@"indentifier"];
}
- (void)tap:(UITapGestureRecognizer *)tap{
NSLog(@"tap");
}
- (void)click:(UIButton *)btn{
NSLog(@"click");
}
一:
我们先点击蓝色BView区域,打印如下
2022-02-27 23:36:43.790545+0800 Test1[18938:281881] CustomCollectionView hitTest:withEvent view = (null)
2022-02-27 23:36:43.791175+0800 Test1[18938:281881] BView hitTest:withEvent view = <BView: 0x7fbd0c90fac0; frame = (120 0; 150 150); layer = <CALayer: 0x60000242d500>>
2022-02-27 23:36:43.791410+0800 Test1[18938:281881] AView hitTest:withEvent view = <BView: 0x7fbd0c90fac0; frame = (120 0; 150 150); layer = <CALayer: 0x60000242d500>>
2022-02-27 23:36:43.791689+0800 Test1[18938:281881] CustomCollectionView hitTest:withEvent view = (null)
2022-02-27 23:36:43.791869+0800 Test1[18938:281881] BView hitTest:withEvent view = <BView: 0x7fbd0c90fac0; frame = (120 0; 150 150); layer = <CALayer: 0x60000242d500>>
2022-02-27 23:36:43.792033+0800 Test1[18938:281881] AView hitTest:withEvent view = <BView: 0x7fbd0c90fac0; frame = (120 0; 150 150); layer = <CALayer: 0x60000242d500>>
2022-02-27 23:36:43.793415+0800 Test1[18938:281881] CustomTapGestureRecognizer touchesBegan
2022-02-27 23:36:43.794059+0800 Test1[18938:281881] BView touchBegin
2022-02-27 23:36:43.794599+0800 Test1[18938:281881] AView touchBegin
2022-02-27 23:36:43.796823+0800 Test1[18938:281881] CustomTapGestureRecognizer touchesEnded
2022-02-27 23:36:43.797780+0800 Test1[18938:281881] tap
2022-02-27 23:36:43.798461+0800 Test1[18938:281881] BView touchesCancelled
2022-02-27 23:36:43.798877+0800 Test1[18938:281881] AView touchesCancelled
从打印上可看出先是subviews 调用hitTest方法逆顺序查找到了第一响应者,之后 触摸事件首先传递到手势上,如果手势识别成功,就会取消事件的继续传递,否则,事件还是会被响应链处理。系统维持了与响应链关联的所有手势,事件首先发给这些手势,然后再发给响应链。
首先,我们的单击事件,是有有手势识别这个大哥来优先获取。只不过,手势识别是需要一点时间的。在手势还是Possible 状态的时候,事件传递给了响应链的第一个响应对象。
这样自然就去调用了响应链UIResponder的touchsBegan:withEvent方法,之后手势识别成功了,就会去cancel之前传递到的所有响应对象,于是就会调用它们的touchsCancelled:withEvent:方法。
Gesture Recognizers Get the First Opportunity to Recognize a Touch.
A window delays the delivery of touch objects to the view so that the gesture recognizer can analyze the touch first. During the delay, if the gesture recognizer recognizes a touch gesture, then the window never delivers the touch object to the view, and also cancels any touch objects it previously sent to the view that were part of that recognized sequence.
Google翻译:
手势识别器获得识别触摸的第一个机会。
一个窗口延迟将触摸对象传递到视图,使得手势识别器可以首先分析触摸。 在延迟期间,如果手势识别器识别出触摸手势,则窗口不会将触摸对象传递到视图,并且还将先前发送到作为识别的序列的一部分的视图的任何触摸对象取消。
图片:
二、
我们再点击红色button,打印如下
2022-02-27 23:56:09.249760+0800 Test1[19172:292581] CustomCollectionView hitTest:withEvent view = (null)
2022-02-27 23:56:09.249960+0800 Test1[19172:292581] BView hitTest:withEvent view = (null)
2022-02-27 23:56:09.250217+0800 Test1[19172:292581] BButton hitTest:withEvent view = <BButton: 0x7fcea7f0e6d0; baseClass = UIButton; frame = (0 0; 100 100); opaque = NO; layer = <CALayer: 0x600001f2dfe0>>
2022-02-27 23:56:09.250393+0800 Test1[19172:292581] AView hitTest:withEvent view = <BButton: 0x7fcea7f0e6d0; baseClass = UIButton; frame = (0 0; 100 100); opaque = NO; layer = <CALayer: 0x600001f2dfe0>>
2022-02-27 23:56:09.250655+0800 Test1[19172:292581] CustomCollectionView hitTest:withEvent view = (null)
2022-02-27 23:56:09.250803+0800 Test1[19172:292581] BView hitTest:withEvent view = (null)
2022-02-27 23:56:09.250991+0800 Test1[19172:292581] BButton hitTest:withEvent view = <BButton: 0x7fcea7f0e6d0; baseClass = UIButton; frame = (0 0; 100 100); opaque = NO; layer = <CALayer: 0x600001f2dfe0>>
2022-02-27 23:56:09.251166+0800 Test1[19172:292581] AView hitTest:withEvent view = <BButton: 0x7fcea7f0e6d0; baseClass = UIButton; frame = (0 0; 100 100); opaque = NO; layer = <CALayer: 0x600001f2dfe0>>
2022-02-27 23:56:09.252759+0800 Test1[19172:292581] CustomTapGestureRecognizer touchesBegan
2022-02-27 23:56:09.253400+0800 Test1[19172:292581] BButton touchesBegan
2022-02-27 23:56:09.257432+0800 Test1[19172:292581] CustomTapGestureRecognizer touchesEnded
2022-02-27 23:56:09.265692+0800 Test1[19172:292581] BButton gestureRecognizerShouldBegin = 0
2022-02-27 23:56:11.384000+0800 Test1[19172:292581] BButton touchesEnded
2022-02-27 23:56:13.575849+0800 Test1[19172:292581] click
我们发现只响应了button的点击事件,按照手势优先于响应链的原则,不应该是view的手势被响应吗,为什么是button的点击事件响应呢
因为某些空间的事件苹果系统设置不响应手势
iOS 开发文档里这样说:
In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer. This applies only to gesture recognition that overlaps the default action for a control, which includes:
A single finger single tap on a UIButton, UISwitch, UISegmentedControl, UIStepper,and UIPageControl.A single finger swipe on the knob of a UISlider, in a direction parallel to the slider.A single finger pan gesture on the knob of a UISwitch, in a direction parallel to the switch.
Google 翻译为:
在iOS 6.0及更高版本中,默认控制操作可防止重叠的手势识别器行为。 例如,按钮的默认操作是单击。 如果您有一个单击手势识别器附加到按钮的父视图,并且用户点击按钮,则按钮的动作方法接收触摸事件而不是手势识别器。 这仅适用于与控件的默认操作重叠的手势识别,其中包括:
单个手指单击UIButton,UISwitch,UISegmentedControl,UIStepper和UIPageControl.
单个手指在UISlider的旋钮上滑动,在平行于滑块的方向上。在UISwitch的旋钮上的单个手指平移手势 与开关平行的方向。
打印button的gestureRecognizerShouldBegin,发现是no
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
手势识别器是否能够开始识别手势.
当手势识别器识别到手势,准备从UIGestureRecognizerStatePossible状态开始转换时.调用此代理,如果返回YES,那么就继续识别,如果返回NO,那么手势识别器将会将状态置为UIGestureRecognizerStateFailed.
所以uibutton是gestureRecognizerShouldBegin返回no,使手势识别失败了
如何使得UIButton的点击事件和view的手势事件同时响应呢
设置tap的cancelsTouchesInView为NO
// default is YES. causes touchesCancelled:withEvent: or pressesCancelled:withEvent: to be sent to the view for all touches or presses recognized as part of this gesture immediately before the action method is called.
设置后的打印为
2022-02-28 00:08:56.732222+0800 Test1[19378:303902] CustomTapGestureRecognizer touchesBegan
2022-02-28 00:08:56.732845+0800 Test1[19378:303902] BButton touchesBegan
2022-02-28 00:08:56.736207+0800 Test1[19378:303902] CustomTapGestureRecognizer touchesEnded
2022-02-28 00:08:56.737133+0800 Test1[19378:303902] tap
2022-02-28 00:08:56.737544+0800 Test1[19378:303902] BButton touchesEnded
2022-02-28 00:08:56.738650+0800 Test1[19378:303902] click
没有调用UIButton的gestureRecognizerShouldBegin,也没有收到touchCancel
三、
点击uicollectionview的cell,发现cell没有被响应,响应的是tap手势事件
2022-02-28 10:27:56.035246+0800 Test1[30890:391092] CustomCell hitTest:withEvent view = (null)
2022-02-28 10:27:56.036396+0800 Test1[30890:391092] CustomCell hitTest:withEvent view = <CustomCell: 0x7fabe2218810; baseClass = UICollectionViewCell; frame = (0 0; 428 200); layer = <CALayer: 0x600001ca1ea0>>
2022-02-28 10:27:56.036637+0800 Test1[30890:391092] CustomCollectionView hitTest:withEvent view = <CustomCell: 0x7fabe2218810; baseClass = UICollectionViewCell; frame = (0 0; 428 200); layer = <CALayer: 0x600001ca1ea0>>
2022-02-28 10:27:56.036823+0800 Test1[30890:391092] AView hitTest:withEvent view = <CustomCell: 0x7fabe2218810; baseClass = UICollectionViewCell; frame = (0 0; 428 200); layer = <CALayer: 0x600001ca1ea0>>
2022-02-28 10:27:56.037154+0800 Test1[30890:391092] CustomCell hitTest:withEvent view = (null)
2022-02-28 10:27:56.037386+0800 Test1[30890:391092] CustomCell hitTest:withEvent view = <CustomCell: 0x7fabe2218810; baseClass = UICollectionViewCell; frame = (0 0; 428 200); layer = <CALayer: 0x600001ca1ea0>>
2022-02-28 10:27:56.037660+0800 Test1[30890:391092] CustomCollectionView hitTest:withEvent view = <CustomCell: 0x7fabe2218810; baseClass = UICollectionViewCell; frame = (0 0; 428 200); layer = <CALayer: 0x600001ca1ea0>>
2022-02-28 10:27:56.037864+0800 Test1[30890:391092] AView hitTest:withEvent view = <CustomCell: 0x7fabe2218810; baseClass = UICollectionViewCell; frame = (0 0; 428 200); layer = <CALayer: 0x600001ca1ea0>>
2022-02-28 10:27:56.039648+0800 Test1[30890:391092] CustomTapGestureRecognizer touchesBegan
2022-02-28 10:27:56.043894+0800 Test1[30890:391092] CustomTapGestureRecognizer touchesEnded
2022-02-28 10:27:56.045082+0800 Test1[30890:391092] tap
UICollectionViewCell 的touchBegin touchCancel没有被调用 为啥呢
打印堆栈信息TableView调用栈红框底部外的第一条调用:_UIGestureEnvironmentSortAndSendDelayedTouches () ,结合delaysTouchesBegan属性的作用和打印结果,此函数的作用是视图的 touches 方法会被延迟到手势识别成功或者失败之后才开始。所以touchBegin没有被调用
如果想要点击响应的是cell的点击事件,而不是view的tap手势,该如何实现呢
CustomTapGestureRecognizer *tap = [[CustomTapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[av addGestureRecognizer:tap];
tap.delegate = self;
实现gestureRecognizer:shouldReceiveTouch:代理
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
// 若为UICollectionViewCell(即点击了collectionViewCell),
if ([touch.view isKindOfClass:[UICollectionViewCell class]]) {
// cell 不需要响应 父视图的手势,保证didselect 可以正常
return NO;
}
//默认都需要响应
return YES;
}
点击打印如下
四:再看一种情形
一个view上增加button,button添加点击事件,再添加一个子view
AView *av = [[AView alloc] initWithFrame:CGRectMake(0, 100, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-100)];
av.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:av];
BButton *bb = [BButton buttonWithType:UIButtonTypeCustom];
bb.frame = CGRectMake(0, 100, 300, 300);
[bb addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
bb.backgroundColor = [UIColor redColor];
[av addSubview:bb];
BView *bv = [[BView alloc] initWithFrame:CGRectMake(0, 0, 150, 150)];
bv.backgroundColor = [UIColor cyanColor];
[bb addSubview:bv];
点击以后打印如下
UIControl 及其子类默认来说,是不会将事件传递下去的。在代码中,可以理解为 UIView 默认会在其 touches 方法中去调用其nextResponder的 touches 方法,而 UIControl 默认不会去调用。这样就做到了,当某个控件接受了事件之后,事件的传递就会终止
touch事件传递给了BButton,但是button的点击没有执行,猜测只有button是第一响应者的时候才会执行,有大牛了解原因的,欢迎补充
五
还是上述布局,AView增加tap手势,点击BView和BButton会打印什么呢?
点击BView,执行手势tap事件
点击BButton,执行click事件
六
父AView上加了一个子view(BView),两个view上都增加手势
点击BView,执行bView的点击手势
如果想要两个手势都执行,
btap.delegate = self;
实现gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:代理,根据请求返回YES/NO
返回YES允许gestureRecognizer与otherGestureRecognizer同时识别.
如果返回NO,分两种情况.1.两个手势都返回NO,那么不会同时识别.如果一个NO,一个YES.可能会同时识别.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
如果想要点击的时候只响应下方view的手势
可以重写shouldRequireFailureOfGestureRecognizer或者shouldBeRequiredToFailByGestureRecognizer代理
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return gestureRecognizer == self.bGes;
}
或者
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return gestureRecognizer == self.aGes;
}
多用于解决滚动冲突,具体案例可见iOS 常见的手势冲突解决方案
补充手势代理
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
手势识别器是否能够开始识别手势.
当手势识别器识别到手势,准备从UIGestureRecognizerStatePossible状态开始转换时.调用此代理,如果返回YES,那么就继续识别,如果返回NO,那么手势识别器将会将状态置为UIGestureRecognizerStateFailed.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
gestureRecognizer : 此对象发送的代理消息.
返回YES允许gestureRecognizer与otherGestureRecognizer同时识别.
如果返回NO,分两种情况.1.两个手势都返回NO,那么不会同时识别.如果一个NO,一个YES.可能会同时识别.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
一般用来重写该方法.来定义什么时候手势识别失败.如果直接返回YES,那么gestureRecognizer与otherGestureRecognizer互斥的话gestureRecognizer识别失败. 可以用tap手势和longPress手势试试.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
和3差不多,注意这个Be,所以是相反的,如果互斥,otherGestureRecognizer识别失败.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
返回手势识别器是否允许检查手势对象.
UIKit将会在touchesBegan:withEvent:方法之前调用这个代理.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press;
返回手势识别器是否允许检查按压(UIPress对象).
UIKit将会在touchesBegan:withEvent:方法之前调用这个代理.
我们可以通过配置手势的属性来改变它的表现,下面介绍三个常用的属性:
cancelsTouchesInView:该属性默认是 true。顾名思义,如果设置成 false,当手势识别成功时,将不会发送 touchesCancelled 给目标视图,从而也不会打断视图本身方法的触发,最后的结果是手势和本身方法同时触发。有的时候我们不希望手势覆盖掉视图本身的方法,就可以更改这个属性来达到效果。
delaysTouchesBegan:该属性默认是 false。在上个例子中我们得知,在手指触摸屏幕之后,手势处于 .possible 状态时,视图的 touches 方法已经开始触发了,当手势识别成功之后,才会取消视图的 touches 方法。当该属性时 true 时,视图的 touches 方法会被延迟到手势识别成功或者失败之后才开始。也就是说,假如设置该属性为 true ,在整个过程中识别手势又是成功的话,视图的 touches 系列方法将不会被触发。
delaysTouchesEnded:该属性默认是 true。与上个属性类似,该属性为 true 时,视图的 touchesEnded 将会延迟大约 0.15s 触发。该属性常用于连击,比如我们需要触发一个双击手势,当我们手指离开屏幕时应当触发 touchesEnded,如果这时该属性为 false,那就不会延迟视图的 touchesEnded 方法,将会立马触发 ,那我们的双击就会被识别为两次单击。当该属性是 true 时,会延迟 touchesEnded 的触发,将两次单击连在一起,来正常识别这种双击手势。