iOS触摸事件处理详解
当你设计一个app的时候,可能会有这样的场景,你想动态的去响应一个事件。例如,在屏幕上的一个触摸事件可能在不同的对象中都发生,并且你不得不决定由哪一个对象来响应这个事件并且尝试去理解怎么样的一个对象接收了到这个事件。
当一个常见的用户事件发生的时候,UIKit会创建一个事件对象Event Object,该对象包含了事件处理所必须得一些信息。然后它会将事件对象置于激活的app事件队列。例如触摸事件,该触摸时事件对象是一系列触摸信息包装集。例如手势事件,该事件是一个动态的变量它取决于你使用了什么框架以及你感兴趣的手势事件类型。
简介
iOS 事件分为三大类
- 触摸事件
- 加速器事件
- 远程控制事件
这篇博客主要讲解触摸事件
触摸事件是我们平时遇到最多的事件,例如单击、长按、滑动等等。当用户点击按钮,到按钮处理回调。整个过程是如何发生,需要什么样的原则,这些都是问题。为了使系统能更加鲜明符合用户的操作逻辑,iOS系统将事件相应过程拆分成两部分:
- 寻找响应链;
- 事件响应,先将事件通过某种规则来分发,找到处理事件的控件,其次是将事件传递分发,响应。
触摸事件
UIEvent
iOS将触摸事件定义为第一个手指开始触摸屏幕到最后一个手指离开屏幕定义为一个触摸事件。用类UIEvent表示。
UITouch
一个手指第一次点击屏,会形成一个UITouch对象,直到离开销毁。表示触碰。UITouch对象能表明了当前手指触碰的屏幕位置,状态。状态分为开始触碰、移动、离开。
根据定义,UIEvent实际包括了多个UITouch对象。有几个手指触碰,就会有几个UITouch对象。
定义代码如下:
@interface UIEvent : NSObject
@property(nonatomic,readonly) UIEventType type NS_AVAILABLE_IOS(3_0);
@property(nonatomic,readonly) UIEventSubtype subtype NS_AVAILABLE_IOS(3_0);
@property(nonatomic,readonly) NSTimeInterval timestamp;
#if UIKIT_DEFINE_AS_PROPERTIES
//UITouch SET
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
//省略部分代码
@end
UIEventType表明了事件类型,UIEvent表示了三大事件。allTouches是该事件的所有UITouch对象的集合。
//UITouch
@interface UITouch : NSObject
@property(nonatomic,readonly) NSTimeInterval timestamp;
@property(nonatomic,readonly) UITouchPhase phase;
@property(nonatomic,readonly) NSUInteger tapCount; // touch down within a certain point within a certain amount of time
@property(nonatomic,readonly) UITouchType type NS_AVAILABLE_IOS(9_0);
@property(ullable,nonatomic,readonly,strong) UIWindow *window;
@property(nullable,nonatomic,readonly,strong) UIView *view;
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers
NS_AVAILABLE_IOS(3_2);
//省略部分代码
@end
//Touch 状态枚举
typedef NS_ENUM(NSInteger, UITouchPhase) {
UITouchPhaseBegan, // whenever a finger touches the surface.
UITouchPhaseMoved, // whenever a finger moves on the surface.
UITouchPhaseStationary, // whenever a finger is touching the surface but hasn't moved since the previous event.
UITouchPhaseEnded, // whenever a finger leaves the surface.
UITouchPhaseCancelled, // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face)
};
UITouch中phase表明了手指移动的状态,包括1.开始点击;2.移动;3.保持; 4.离开;5.被取消(手指没有离开屏幕,但是系统不再跟踪它了)
综上,UIEvent就是一组UITouch。每当该组中任何一个UITouch对象的phase发生变化,系统都会产生一条TouchMessage。也就是说每次用户手指的移动和变化,UITouch都会形成状态改变,系统变回会形成Touch message进行传递和派发。那么 一次触摸事件是由一组UITouch对象状态变化引起的一组Touch message的转发和派送。那么事件派发的原则是什么?
响应者
我们先来了解一下什么是响应者。
只要继承了UIResponder的对象就可以作为事件的响应者,下面看一下 UIResponder及其子类的继承关系:
平常开发中所使用到的控件例如:UIButton,UiView,UIViController,APPDelegate,UIApplication等都能响应事件。与用户交互的控件就是第一响应者,它将作为响应者链的开始,事件首先发送给第一响应者,然后再依次传递下去,直到该事件被某个响应者处理。
响应链
响应链是“事件派发”的原则和规定,那么响应链是什么?顾名思义事件链是一个链条,详细的定义如下:
- 每条链是一个 链表状结构,整个是一棵树
- 链表的每一个node是一个 UIResponser对象
UIResponser,响应链中的响应者,用来接收和处理事件的类,先抛开iOS中的具体传递细节,系统发送UIEvent的Touch message给UIResponser类。UIResponser提供了一下几个函数来做事件处理
//触摸事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
//物理按钮,遥控器上面的按钮在按压状态等状态下的回调
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
//设备的陀螺仪和加速传感器使用
- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
UIResponser包括了各种Touch message 的处理,比如开始,移动,停止等等。
回到响应链,响应链是由UIResponser组成的,那么是按照哪种规则形成的。
- 程序启动
- UIApplication会生成一个单例,并会关联一个APPDelegate。APPDelegate作为整个响应链的根建立起来,而UIApplication会将自己与这个单例链接,即UIApplication的nextResponser(下一个事件处理者)为APPDelegate。
- 创建UIWindow
- 程序启动后,任何的UIWindow被创建时,UIWindow内部都会把nextResponser设置为UIApplication单例。
- UIWindow初始化rootViewController, rootViewController的nextResponser会设置为UIWindow
- UIViewController初始化
- loadView, VC的view的nextResponser会被设置为VC。
- addSubView
- addSubView操作过程中,如果子subView不是VC的View,那么subView的nextResponser会被设置为superView。如果是VC的View,那就是 subView -> subView.VC ->superView
- 如果在中途,subView.VC被释放,就会变成subView.nextResponser = superView
最终形成类似这样一张图
其中应该是由箭头的,箭头的方向是朝上,也就是subView指向superView.
事件传递
有了响应网为基础,事件的传递就比较简单,只需要选择其中一条响应链,但是选择那一条响应链来传递呢?为了弄清真个过程,我们先来查看一下从触摸硬件事件转化为UIEvent消息。
- 首先用户触摸屏幕,系统的硬件进程会获取到这个点击事件,将事件简单处理封装后存到系统中,由于硬件检测进程和当前运行的APP是两个进程,所以进程两者之间传递事件用的是端口通信。硬件检测进程会将事件放入到APP检测的那个端口。
- 其次,APP启动主线程RunLoop会注册一个端口事件,来检测触摸事件的发生。当时事件到达,系统会唤起当前APP主线程的Runloop。唤起原因就是端口触摸事件,主线程会分析这个事件。
- 最后,系统判断该次触摸是否导致了一个新的事件, 也就是说是否是第一个手指开始触碰,如果是,系统会先从响应网中 寻找响应链。如果不是,说明该事件是当前正在进行中的事件产生的一个Touch message, 也就是说已经有保存好的响应链。
如果是新事件,系统会寻找响应链,为了符合用户的操作习惯,系统会根据用户的点击位置,在当前的整个APP的显示层级中寻找。过程如下:
- 将所有的显示在屏幕上的 “合格的”UIWindow对象 按照层级结构从上到下排列成一个数组。
- 从第一个UIWindow对象开始,先判断UIWindow是否合格,其次判断 点击位置在不在这个Window内,如果不在 ,返回nil, 就换下一个UIWindow;如果在的话,并且UIWindow没有subView就返回自己,整个过程结束。如果UIWindow有subViews,就从后往前遍历整个subViews,做和UIWindow类似的事情,直到找到一个View。如果没有找到到就不做传递。
- 合格的UIWindow,UIView。意思是控件被允许接受事件。符合三个条件:1.不能被隐藏;2.alpha值大于0.01(不是backgroundColor为clearColor);3.isUserInteractionEnabled为YES,打开状态。一般UILabel,UIImageView纯显示的控件默认是关闭状态,也就是不处理事件。
显示控件有了两个方法来做上面这件事,就是常说的hitTest
// 先判断点是否在View内部,然后遍历subViews
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
//判断点是否在这个View内部
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds
整个过程的系统实现大致如下
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event {
//判断是否合格
if (!self.hidden && self.alpha > 0.01 && self.isUserInteractionEnabled) {
//判断点击位置是否在自己区域内部
if ([self pointInside: point withEvent:event]) {
UIView *attachedView;
for (int i = self.subviews.count - 1; i >= 0; i--) {
UIView *view = self.subviews[i];
//对子view进行hitTest
attachedView = [view hitTest:point withEvent:event];
if (attachedView)
break;
}
if (attachedView) {
return attachedView;
} else {
return self;
}
}
}
return nil;
}
技巧
以上可知默认情况下,用户点击哪个View,系统就会在寻找过程中返回哪个view,但是我们可以重载上面两个方法做如下事情:
- 将控件外部点规整到控件内部。 例如控件较小,点击位置在控件边缘外部,可以重载- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; 将外部的点也判断为内部点,这样hitTest就会遍历自己。
- 重载HitTest更改默认行为。 有时候点击subView的某些特殊位置需要superView处理,我们可以在superView的hitTest,返回superView。这样superView变成首部响应者
hitTest的逻辑代码中会把隐藏,透明(alpha<0.01,不是backgroundColor为clearColor),不交互的view滤过,但不代表hitTest不会被调用,我们可以重载hitTest去让 已经隐藏、透明、不交互的view响应事件。不过最正规的方法是打开控件交互属性。
以上过程返回的View被称作hitTestView,顺着hitTestView的nextResponser,可以形成一个链,即响应链。 最后指向appDelegate. 并且返回hitTestView之后,系统会持有hitTestView。事件不结束,这个hitTestView不会发生变化,即使用户点击之后将手指移动到其他控件上面,该点击都会绑定开始的hitTestView。当所有手指离开屏幕,事件结束。再次点击,事件重新开始。以上过程再来一次。
事件响应
形成响应链之后,UIWindow会把事件目标锁定为hitTestView(响应链头的控件),当手指状态发生变化, 会不停的发送UITouch Message 给这个hitTestView。 下面这几个方法会被调用。
然后控件的以下方法会陆续被调用
//点击刚开始,回调这个方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//点击之后移动,回调这个方法
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指移开,点击结束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//点击过程中,事件被手势识别,会回调这个方法,关于手势后面会讲解
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
技巧
由于系统只会把事件发送给 hitTestView,如果你想让hitTestView之后的其他响应者处理该Touch Mesage ,需要自己实现以上几个方法做派发,例如
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
//do someThiing
[self.nextResponser touchesBegan: touches withEvent:event];
}
事件转发可以做很多事情。大家可以尽可能的想象
手势处理
以上看来所有的事情都很平稳,无非就是寻找响应链,传递事件等等。但是接下来大家可能需要蒙圈。先来道题目
- AView 有子view BView,AView上面有一个单击手势,这个时候点击BView。默认情况下,Bview的四个Touch方法中,那些方法会被调用?
可能很多人会说没有任何影响,基本都会调用,答案是整个过程会调用这两个方法。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
touchEnd不会被调用。
为什么?因为有手势的存在,我们先看一下手势。
手势
手势是苹果为处理常用的用户交互所推出了一个优先级更高的处理技术。为了让用户完成对多种控件的基本操作,苹果实现了以下几个手势
UITapGestureRecognizer
UIPinchGestureRecognizer
UIRotationGestureRecognizer
UISwipeGestureRecognizer
UIPanGestureRecognizer
UIScreenEdgePanGestureRecognizer
UILongPressGestureRecognizer
上面包括点击,长按,旋转,滑动等等手势。这样开发者就可以随便将其关联到某个控件上完成交互。
先抛开刚才的问题,先看单纯的手势如何识别用户操作。
系统会将用户触摸屏幕的点事件 发送给手势,手势会根据具体的点击位置和序列,判断是否是某种特定行为。具体的判断方法如下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
和UIResponser一样,手势也有这几个方法,点击的每个阶段手势都会响应不同的方法,手势会在以上四个方法中去对手势的State做更改,手势的State表明当前手势是识别还是失败等等。比如单击手势会在touchesBegan 时记录点击位置,然后在touchesEnded判断点击次数、时间、是否移动过,最后得出是否识别该手势。这几个方法一般在自定义手势里面使用。
手势状态
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
//未知状态
UIGestureRecognizerStatePossible, // the recognizer has not yet recognized its gesture, but may be evaluating touch events. this is the default state
//首次识别状态,对于连续手势,例如长按,有这种状态
UIGestureRecognizerStateBegan, // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop
//再次识别,当手连续手势识别之后,再次受到touch事件
UIGestureRecognizerStateChanged, // the recognizer has received touches recognized as a change to the gesture. the action method will be called at the next turn of the run loop
//识别完成,受到touchend 消息之后
UIGestureRecognizerStateEnded, // the recognizer has received touches recognized as the end of the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible
//取消识别
UIGestureRecognizerStateCancelled, // the recognizer has received touches resulting in the cancellation of the gesture. the action method will be called at the next turn of the run loop. the recognizer will be reset to UIGestureRecognizerStatePossible
//识别失败
UIGestureRecognizerStateFailed, // the recognizer has received a touch sequence that can not be recognized as the gesture. the action method will not be called and the recognizer will be reset to UIGestureRecognizerStatePossible
// Discrete Gestures – gesture recognizers that recognize a discrete event but do not report changes (for example, a tap) do not transition through the Began and Changed states and can not fail or be cancelled
//识别状态,与识别结束一个意思
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible
};
手势的状态有以上几种,我们来看手势的整个迁移过程,先明确几个信息
- 手势的状态迁移只有在它们收到Touch message的时候,才能做状态变化处理代码。
- 手势分为连续状态手势和不连续状态手势。连续手势有长按,慢滑等。不连续手势有单击,双击等等。
- 当用户没有点击屏幕,所有手势都处于Possiable状态。
当用户点击屏幕,手势会收到Touch Began Message, 手势的touchBegan方法会被调用。手势开始记录点击位置和时间。仍处于Possiable状态。如果用户按住不放,间隔超过一定时间,单击手势会变化为失败状态,并在下个一runloop变为possiable。如果时间大于长按手势设定时间,长按手势就会变化为Began状态,当用户移动手指,长按手势的touch move方法被调用,长按手势将自己状态设置为Change,并且也会回调处理方法。最后手指离开,系统调用长按手势touchEnd方法,手势状态回归为Recognized状态。
手势混合处理
如果一个View上既有单击,又有双击,用户点击该view两次, 默认情况下,单击被处理,双击不管用。因为默认情况下,一旦事件被某个手势处理,第二个手势会识别失败 幸运的是苹果提供了方法让我们修改这种默认行为,具体的方法如下
@protocol UIGestureRecognizerDelegate <NSObject>
@optional
// called when a gesture recognizer attempts to transition out of UIGestureRecognizerStatePossible. returning NO causes it to transition to UIGestureRecognizerStateFailed
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
// called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other
// return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)
//
// note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
// called once per attempt to recognize, so failure requirements can be determined lazily and may be set up between recognizers across view hierarchies
// return YES to set up a dynamic failure requirement between gestureRecognizer and otherGestureRecognizer
//
// note: returning YES is guaranteed to set up the failure requirement. returning NO does not guarantee that there will not be a failure requirement as the other gesture's counterpart delegate or subclass methods may return YES
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
// called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
// called before pressesBegan:withEvent: is called on the gesture recognizer for a new press. return NO to prevent the gesture recognizer from seeing this press
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press;
@end
上面是手势的代理方法,你可以实现手势的这几个代理方法,更改默认行为。
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
手势已经应分析出该事件可以响应,再对自己的状态进行更改之前,会询问代理的这个方法是否允许更改。默认为YES,如果你实现并设置为NO,那么手势会变为失败状态,这个可以用在手势只识别View的某几个区域的相应。 - (BOOL)gestureRecognizer:(UIGestureRecognizer )gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer )otherGestureRecognizer;
当两个手势都对该事件进行识别,但只有一个能响应,另外一个会失败。比如一个View上绑定两个单击事件。为了让两个手势都响应,我们可以实现此方法,让两个手势都响应。 - (BOOL)gestureRecognizer:(UIGestureRecognizer )gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer )otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
- (BOOL)gestureRecognizer:(UIGestureRecognizer )gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer )otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
这两个方法是iOS 7引入的,目的是让两个手势之间增加依赖,比如单击和双击,如果需要单击在双击失败的情况下识别,那么可以实现这两个方法。 - (BOOL)gestureRecognizer:(UIGestureRecognizer )gestureRecognizer shouldReceiveTouch:(UITouch )touch;
- (BOOL)gestureRecognizer:(UIGestureRecognizer )gestureRecognizer shouldReceivePress:(UIPress )press;
这两个方法是判断手势在新的Touch和Press Began阶段是否关注该UITouch和UIPress对象,默认为YES,如果设置为NO,手势不会关注该Touch的任何状态变化。
手势与事件响应
回到我们上面问过的问题,BView只有touchBegan, touchesCancelle 的原因是什么?答案在于整个触摸事件全过程
- 系统会通过hitTest的方法寻找响应链,完成之后会形成下图模型。
图中最右边是响应链,中间是关联在相应链在视图上的手势
- 有了模型之后就会发生图上的三个步骤
第一步:系统会将所有的 Touch message 优先发送给 关联在响应链上的全部手势。手势根据Touch序列消息和手势基本规则更改自己的状态(有的可能失败,有的可能识别等等)。如果没有一个手势对Touch message 进行拦截(拦截:系统不会将Touch message 发送给响应链顶部响应者),系统会进入第二步
第二步:系统将Touch message 发送给响应链 顶部的 视图控件,顶部视图控件这个时候就会调用Touch相关的四个方法中的某一个。之后进入自定义Touch message转发
第三步:自定义Touch message转发可以继承UIResponser的四个Touch函数做转发。
解释一下第一步中说的拦截,手势会表明是否拦截该Touch Message,主要由下面三个属性控制。
再回到那道题目,如果我们想hitTestView的toucheEnd函数依然能得到调用,怎么办?其实UIGestureRecognizer有三个属性
@property(nonatomic) BOOL cancelsTouchesInView; // 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.
@property(nonatomic) BOOL delaysTouchesBegan; // default is NO. causes all touch or press events to be delivered to the target view only after this gesture has failed recognition. set to YES to prevent views from processing any touches or presses that may be recognized as part of this gesture
@property(nonatomic) BOOL delaysTouchesEnded; // default is YES. causes touchesEnded or pressesEnded events to be delivered to the target view only after this gesture has failed recognition. this ensures that a touch or press that is part of the gesture can be cancelled if the gesture is recognized
- cancelsTouchesInView
默认为YES,表明当手势识别了该事件,系统会将Touch cancel消息发送给hitTestView ,并调用hitTestView的TouchCancel。设置为NO,不会再收到TouchCancel - delaysTouchesBegan
默认为NO, 表明无论什么情况下,不会拦截Touch began消息。如果设置为YES,只要有一个手势不识别失败,都不会发送Touch began到响应链的第一响应者。 - delaysTouchesEnded
默认为NO, 和delaysTouchesBegan类似,不过它是用来控制TouchEnd message的拦截
总结
iOS整个事件处理的过程就是这样,系统为完成整个交互做了很多东西,核心点如下:
- 事件分发过程分为:1.寻找响应链;2.事件消息分发
- 响应网是事件响应的基础,响应链是事件响应的具体路径。
- 事件消息分发优先发送给手势集合,手势内部会做冲突处理,过滤消息。不被过滤的消息会传递给响应链对象。