您现在的位置是:首页 >技术杂谈 >【iOS】--响应者链网站首页技术杂谈
【iOS】--响应者链
响应者链
iOS响应者链是一种事件处理机制,它描述了在iOS系统中,当一个事件(比如用户点击屏幕或者发送手势)发生时,它将从触发事件的源头开始,按照预定义的顺序传递给各个视图或控件进行处理,直到最终被处理或被丢弃。
- 从本质上讲,苹果设备响应事件的整个过程可以分为两个步骤:
- 步骤1:寻找目标。在iOS视图层次结构中找到触摸事件的最终接受者;
- 步骤2:事件响应。基于iOS响应者链(Responder Chain)处理触摸事件。
响应者
响应者链中的各个对象被称为“响应者”(responder),响应者链的根节点是UIApplication对象,所有的事件都从UIApplication对象开始传递。当事件发生时,UIApplication对象首先将其传递给当前显示在屏幕上的UIWindow对象,然后递归地向下传递给其子视图,依次传递给UIViewController、UIView等响应者对象进行处理,直到事件被处理完毕或被丢弃。
在响应者链中,每个响应者对象都可以处理事件,也可以选择将事件传递给下一个响应者对象进行处理,或者直接丢弃事件。响应者链中的每个响应者对象都可以重写几个方法来处理事件,这些方法包括touchesBegan:withEvent:、touchesMoved:withEvent:、touchesEnded:withEvent:等等。
响应者链的机制在iOS开发中非常重要,它确保了事件可以在正确的对象中得到处理,保证了iOS应用的用户交互和响应体验。
响应者事件
- iOS 中的事件可分为:触摸事件(multitouch events)、加速计事件(accelerometer events)、远程控制事件(remote control events)。
事件表
响应者链 Responder Chain概览
响应者对象
首先,在iOS里能够处理事件的对象被称为响应者对象,但是不是任何对象都能够处理响应者事件。
在iOS中,响应者对象是指能够响应用户事件并进行相关处理的对象,它们通常是UIView或其子类,也可以是UIViewController或其子类。响应者对象都实现了UIResponder协议,并提供了一些方法来响应用户事件,比如touchesBegan:withEvent:、touchesMoved:withEvent:、touchesEnded:withEvent:等方法
这里的实现UIResponder协议指UIApplication、UIViewController、UIView 都继承于 UIResponder。
常见的响应者对象
- UIView:是iOS中最基本的用户界面元素,可以接收用户的触摸事件并进行相关的处理。
- UIViewController:作为MVC模式中的控制器,可以响应用户的触摸事件,同时还可以管理一个或多个视图控制器。
- UIWindow:是整个应用程序的窗口,它包含了一个或多个视图,并且是接收和处理触摸事件的最高层响应者对象。
- UIGestureRecognizer:是iOS中专门用来处理手势事件的响应者对象,包括UITapGestureRecognizer、UIPanGestureRecognizer、UILongPressGestureRecognizer等等。
- UIScrollView:是一个可以滚动的视图控件,它可以接收用户的触摸事件,并在触摸拖动时进行滚动。
- UITableView:是iOS中常用的列表视图控件,它可以显示大量的数据,并且可以处理用户的滑动、点击等事件。
UIResponder
UIResponder是iOS中的一个基类,定义了一些接口,用于处理触摸事件和键盘事件等用户事件。所有能够接收并处理事件的对象都继承自UIResponder。
- UIResponder 内部提供了以下方法来处理触摸事件
// 一根或者多根手指开始触摸view,系统会自动调用view的下面方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 一根或者多根手指离开view,系统会自动调用view的下面方法
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法[可选]
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
通过重写UIResponder中定义的方法,开发者可以在自己的类中处理用户事件,并做出相应的响应。
- UIResponder加速器事件 motionBegan:withEvent:、motionEnded:withEvent:、motionCancelled:withEvent: 是 UIResponder 中定义的三个方法,用于处理设备运动事件。这些事件包括摇晃、倾斜、加速等设备运动。
注意⚠️:这些方法可以被任何继承自 UIResponder 的对象重写,在自己的类中处理设备运动事件,并进行相应的响应。需要注意的是,只有在设备支持相关的运动事件时,这些方法才会被调用。
- UIResponder远程控制事件
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);
remoteControlReceivedWithEvent 是一个 iOS 中的 UIResponder 方法,用于接收远程控制事件。这些远程控制事件可以来自于耳机、锁屏界面和控制中心等,例如暂停音乐、切换歌曲等。
当接收到远程控制事件时,系统会将事件封装成一个 UIEvent 对象,然后将该对象传递给当前响应者对象的 remoteControlReceivedWithEvent 方法。在该方法中,开发者可以获取 UIEvent 对象,并根据事件类型进行相应的处理。
需要注意的是,**只有当应用程序处于播放状态时,才会接收到远程控制事件。**如果应用程序没有进行播放,系统不会向应用程序发送远程控制事件。此外,在使用远程控制功能时,开发者还需要在应用程序的 Info.plist 文件中声明相应的背景模式。
UITouch
- 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的 UITouch 对象,一根手指对应一个 UITouch 对象。
- UITouch 的作用:保存着跟手指相关的信息,比如触摸的位置、时间、阶段。当手指移动时,系统会更新同一个 UITouch 对象,使之能够一直保存该手指的触摸位置;当手指离开屏幕时,系统会销毁相应的 UITouch 对象。
- 很强大:UITouch 对象在触摸事件的过程中会不断更新,直到触摸事件结束。在触摸事件的过程中,系统会不断向响应链中的响应者发送触摸事件,并将触摸事件封装成 UIEvent 对象进行传递。当触摸事件结束时,系统会销毁相应的 UITouch 对象
UITouch的属性
// 记录了触摸事件产生或变化时的时间,单位是秒 The relative time at which the acceleration event occurred(read-only)
@property(nonatomic,readonly) NSTimeInterval timestamp;
// 当前触摸事件所处的状态
@property(nonatomic,readonly) UITouchPhase phase;
// touch down within a certain point within a certain amount of timen 短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic,readonly) NSUInteger tapCount;
@property(nonatomic,readonly) UITouchType type NS_AVAILABLE_IOS(9_0);
// 触摸产生时所处的窗口
@property(nullable,nonatomic,readonly, strong) UIWindow *window;
// 触摸产生时所处的视图
@property(nullable,nonatomic,readonly, strong) UIView *view;
// The gesture-recognizer objects currently attached to the view.
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers
UITouch的方法
/*返回值表示触摸在view上的位置
这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置*/
- (CGPoint)locationInView:(nullable UIView *)view;
// 该方法记录了前一个触摸点的位置
- (CGPoint)previousLocationInView:(nullable UIView *)view;
// Use these methods to gain additional precision that may be available from touches.
// Do not use precise locations for hit testing. A touch may hit test inside a view, yet have a precise location that lies just outside.
//获取指定视图上的精确触摸位置,该方法会考虑到多点触控时不同触点之间的偏移。
- (CGPoint)preciseLocationInView:(nullable UIView *)view API_AVAILABLE(ios(9.1));
// 获取指定视图上上一次触摸的精确位置。
- (CGPoint)precisePreviousLocationInView:(nullable UIView *)view API_AVAILABLE(ios(9.1));
UIEvent
UIEvent 是 iOS 中用于表示触摸事件的类,一个 UIEvent 对象包含了所有与触摸事件相关的信息,比如触摸的位置、时间、阶段,以及多点触控时不同触点之间的状态等等。
UIEvent 对象是由系统自动创建和管理的,通常情况下不需要手动创建。每个 UIEvent 对象都与一个或多个 UITouch 对象相关联,这些 UITouch 对象保存着触摸事件的具体信息,比如触摸的位置和时间。
每产生一个事件,就会产生一个 UIEvent 对象,UIEvent 称为事件对象,记录事件产生的时刻和类型。
- 事件类型属性
//事件类型,枚举值包括触摸、运动、遥控等。
@property(nonatomic,readonly) UIEventType type NS_AVAILABLE_IOS(3_0);
// 事件子类型,对于触摸事件,其子类型包括touch down、touch move、touch up等。
@property(nonatomic,readonly) UIEventSubtype subtype NS_AVAILABLE_IOS(3_0);
- 产生时间的事件属性
事件发生的时间戳,单位为秒。
@property(nonatomic,readonly) NSTimeInterval timestamp;
获取touch
刚才说到每个 UIEvent 对象都与一个或多个 UITouch 对象相关联,这些 UITouch 对象保存着触摸事件的具体信息,比如触摸的位置和时间,下面看看如何产生关系
这是总的方法
- (nullable NSSet <UITouch *> *)allTouches;
- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;
- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
- (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture NS_AVAILABLE_IOS(3_2);
// An array of auxiliary UITouch’s for the touch events that did not get delivered for a given main touch. This also includes an auxiliary version of the main touch itself.
- (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0);
// An array of auxiliary UITouch’s for touch events that are predicted to occur for a given main touch. These predictions may not exactly match the real behavior of the touch as it moves, so they should be interpreted as an estimate.
- (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch NS_AVAILABLE_IOS(9_0);
- alltouches
@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;
allTouches 是 UIEvent 类中的一个只读属性,用于获取与当前事件关联的所有 UITouch 对象的集合。
它返回的是一个包含 UITouch 对象的集合,可以通过该集合来获取当前事件中的所有手指相关的信息,如触摸位置、时间、阶段等。在多点触控的场景下,该集合中包含多个 UITouch 对象,每个对象对应一个手指。
- 返回touches集合
// 返回发生在指定窗口上的UITouch对象集合。
- (nullable NSSet <UITouch *> *)touchesForWindow:(UIWindow *)window;
// // 返回发生在指定视图上的UITouch对象集合。
- (nullable NSSet <UITouch *> *)touchesForView:(UIView *)view;
- touchesForGestureRecognizer
touchesForGestureRecognizer 是 UIEvent 类中的一个方法,用于获取与指定手势识别器相关联的所有 UITouch 对象的集合。当手势识别器被激活时,系统会将当前事件传递给它,手势识别器会根据事件的信息来识别用户的手势。该方法可以用于获取与当前手势识别器相关的所有手指信息,以便手势识别器对手势进行更准确的识别。
该方法需要传入一个 UIGestureRecognizer 对象作为参数,用于指定要获取哪个手势识别器相关联的 UITouch 对象的集合。它返回的是一个包含 UITouch 对象的集合,用于表示与指定手势识别器相关联的所有手指信息。
该方法可以通过以下方式访问:
- (nullable NSSet <UITouch *> *)touchesForGestureRecognizer:(UIGestureRecognizer *)gesture API_AVAILABLE(ios(3.2));
- coalescedTouchesForTouch
- (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch API_AVAILABLE(ios(9.0));
- predictedTouchesForTouch
所谓predict就是预测
predictedTouchesForTouch(_) 是 UIEvent 类的一个方法,用于获取未来几个周期中可能发生的触摸事件,以帮助在下一个周期中更准确地处理触摸事件。
在 iOS 中,由于触摸事件的处理通常需要花费一定的时间,因此在用户快速滑动或快速触摸时,可能会出现丢失触摸事件或处理不准确的情况。predictedTouchesForTouch(_) 方法可以在用户快速移动手指时提供额外的触摸事件预测,从而改善触摸事件的响应速度和精度。
具体而言,predictedTouchesForTouch(_) 方法可以返回一系列 UITouch 对象,这些对象描述了未来几个周期中可能发生的触摸事件**。这些事件是由系统根据当前触摸事件的历史记录和其他信息预测出来的,并不是实际发生的触摸事件。开发者可以利用这些预测事件来更准确地响应触摸事件,以提高应用程序的性能和响应速度。
- (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch API_AVAILABLE(ios(9.0));```
完整的响应者链
- 从本质上讲,苹果设备响应事件的整个过程可以分为两个步骤:
- 步骤1:寻找目标。在iOS视图层次结构中找到触摸事件的最终接受者; (寻找命中者)
- 步骤2:事件响应。基于iOS响应者链(Responder Chain)处理触摸事件。(寻找响应者)
寻找目标是通过UIView的以下两个方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;//这个方法返回目标view
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; //这个方法判断触摸点是否在当前view范围内
- 响应者链(Responder Chain)是指在 iOS 应用程序中处理事件(比如触摸、摇晃等)的一种机制,基于 UIKit 框架的响应者对象(UIResponder)组成一个层级结构,层级结构的最上层是应用程序的窗口(UIWindow),最底层是 UIView 或其子类,响应者链的作用是在事件发生时,将事件逐级传递给层级结构中的 UIResponder 对象,直到找到最适合处理该事件的对象为止。
- 一个事件响应者的完成主要经过两个过程:hitTest 方法命中视图和响应者链确定响应者。hitTest 方法首先从顶部 UIApplication 往下调用(从父类到子类),直到找到命中者,然后从命中者视图沿着响应者链往上传递寻找真正的响应者
-
- 用户在设备上进行操作,比如点击按钮、拖动视图等,这个操作被转化为一个 UIEvent 事件。
-
- UIEvent 事件首先被传递给应用程序的主窗口(UIApplication对象),UIApplication 会将事件分发给当前活跃的窗口(UIWindow对象)。
-
- UIWindow 会将事件分发给它上面的视图(UIView对象)。
-
- 视图会依次将事件传递给它的子视图,直到找到一个响应事件的视图,这个视图成为第一响应者。
-
- 第一响应者会执行与事件相关的方法,比如 touchUpInside 等。
-
- 如果第一响应者无法处理事件,事件会沿着响应者链向上传递,直到找到可以处理事件的对象为止。
-
- 如果没有响应事件的对象,则事件会被丢弃。
寻找响应者的hitTest方法
hitTest 是 iOS 中用于响应者链中事件响应查找的方法,它会在视图层次结构中递归地查找能够响应触摸事件的视图对象,并返回最终响应者
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
其中,point 参数表示触摸点在当前视图的坐标系下的位置,event 参数表示触摸事件对象。返回值为最终响应触摸事件的视图对象,如果没有合适的响应视图,则返回 nil。
流程:
- 当触摸事件发生后,系统会将触摸事件以 UIEvent 的方式加入到 UIApplication 的事件队列中,UIApplication 将事件分发给根部的 UIWindow 去处理,UIWindow 则开始调用 hitTest 方法进行迭代命中检测。
- hitTest函数的过程是一个迭代过程
- hitTest 方法会首先判断自己是否能够接收事件,如果能,则继续递归地调用自己的子视图对象,直到找到最终的响应者或没有响应者。在查找过程中,还可以通过设置 userInteractionEnabled 属性为 NO 的视图对象来防止其响应事件。
- 当找到最终响应者后,hitTest 方法会返回该视图对象,UIApplication 对象会将该事件发送给该视图对象进行处理。在这个过程中,所有经过的视图都可以收到该事件的消息,但只有最终响应者才会真正处理该事件。
- 需要注意的是,在实现自定义视图时,如果需要响应事件,必须重写 hitTest 方法,并且确保该方法返回的是能够响应该事件的视图对象
- 通过命中测试找到命中者后,任务并没有完成,因为最终的命中者不一定是事件的响应者。所谓的响应就是开发中为事件绑定的一个触发函数,事件发生后执行响应函数里的代码,例如通过 addTarget 方法为按钮的单击事件绑定响应函数,在按钮被单击后能及时执行想要执行的任务。
模拟hitTest方法
// hitTest : withEvent: 作用:找做合适的view;当事件传递给一个控件的时候调用
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(@"%s",__func__);
// [super hitTest:point withEvent:event];// 使用系统默认做法
// 1、判断自己能否接受事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
// 结束事件传递
return nil;
}
// 2、点是否在自己身上
if (![self pointInside:point withEvent:event]) {
return nil;
}
// 3、判断自己的子控件,去找有没有比自己更合适的view;从后往前遍历自己的子控件
for (int i = self.subviews.count-1; i >= 0; i--) {
// 获取子控件
UIView *childView = [self subviews][i];
// 坐标系转换
CGPoint childPoint = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childPoint withEvent:event];
if (fitView) {
return fitView;
}
}
return self;
}
寻找响应者的pointInside方法
pointInside:withEvent: 方法是 UIView 的方法,用于判断指定的点是否在当前视图内。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
- 其中,point 表示需要检查的点坐标,event 表示与此事件相关联的事件对象。
- 当调用该方法时,系统会首先判断该点是否在当前视图的 bounds 内,如果不在,直接返回 NO;否则继续判断是否在当前视图的子视图中,如果有子视图包含该点,则递归调用该子视图的 pointInside:withEvent: 方法,直到找到包含该点的最底层子视图
- 最后,如果没有子视图包含该点,则当前视图就是最底层的视图,返回 YES,表示该点在当前视图内。
完整的触摸过程
一次完整的触摸过程,会经历 3 个状态。 上面说了完整的响应事件有如下四个事件
触摸开始:- (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
完整的触摸过程如何实现
一个完整的触摸事件流程通常包括以下几个步骤:
- 手指触摸到屏幕,系统会创建一个与手指相关联的 UITouch 对象,并将其加入到系统中的事件队列中。
- 系统会将该事件发送给当前 UIWindow 对象,即调用 UIWindow 对象的 touchesBegan(_:with:) 方法,并将该事件传递给子视图。
- 从根视图开始,系统会通过递归调用 hitTest(_:with:) 方法,寻找响应该事件的视图。在每个视图中,系统都会调用 point(inside:with:) 方法,判断该视图是否包含该事件的触摸点。
- 一旦找到了响应该事件的视图,系统会将该事件发送给该视图,即调用该视图的 touchesBegan(:with:) 方法。
在该视图的 touchesBegan(:with:) 方法中,开发者可以对该事件做出相应的处理,比如更改视图的状态、更新视图的内容等。 - 如果该事件需要传递给其它视图进行处理,开发者可以手动调用 next 方法,将该事件传递给下一个响应者。
当手指离开屏幕时,系统会将一个 touch 对象的 phase 属性设置为 .ended,并将该 touch 对象从事件队列中移除。 - 当前的 UIWindow 对象会将该事件发送给响应者链中的下一个响应者。如果没有下一个响应者,则该事件的响应过程结束。
举例
- 4 个触摸事件处理方法中,都有 NSSet *touches 和 UIEvent *event 两个参数。
- 一次完整的触摸过程中,只会产生一个事件对象,4 个触摸方法都是同一个 event 参数。
- 如果两根手指同时触摸一个 view,那么 view 只会调用一次 touchesBegan:withEvent: 方法,touches 参数中装着 2 个 UITouch 对象。
- 如果这两根手指一前一后分开触摸同一个 view,那么 view 会分别调用 2 次 touchesBegan:withEvent: 方法,并且每次调用时的 touches 参数中只包含一个 UITouch 对象。
- 根据 touches 中 UITouch 的个数可以判断出是单点触摸还是多点触,判断多少次点击:UITouch 的属性 @property(nonatomic,readonly) NSUInteger tapCount;
- 事件的产生和传递:
-
- 发生触摸事件后,系统会将该事件加入到一个由 UIApplication 管理的事件队列中;
-
- UIApplication 会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序的主窗口(keyWindow);
-
- 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,但是这仅仅是整个事件处理过程的第一步;
-
- 找到合适的视图控件后,就会调用视图控件的 touches 方法来作具体的事件处理
UIView 不接收触摸事件的三种情况:
- 不接收用户交互userInteractionEnabled = NO;
- 隐藏 hidden = YES;
- 透明 alpha = 0.0 ~ 0.01。
当一个 UIView 的 alpha 属性的值接近于 0.0 时,视图实际上变得透明,即不可见。当 alpha 设置为 0.0 时,视图完全透明,不会显示在屏幕上。由于不可见,用户无法与其进行交互或触发事件。
在 iOS 中,用户交互是通过触摸事件来处理的。当一个视图的 alpha 属性小于等于 0.01 时,它几乎是不可见的,甚至对于触摸事件也是如此。这是因为系统会忽略对透明度低于一定阈值的视图的触摸事件。这是为了提高性能和减少用户交互的复杂性。
如果你想让一个视图透明并且仍然能够响应触摸事件,你可以考虑设置一个较低的透明度值,但不要将其完全设置为 0.0。例如,你可以将 alpha 设置为 0.01 或更高的值,以确保视图在屏幕上可见,但仍然具有较低的透明度。这样,视图将保持可见性并能够响应触摸事件。
- UIImageView 的 userInteractionEnabled 默认就是 NO,因此UIImageView 以及它的子控件默认是不能接收触摸事件的。
iOS的响应者链的传递过程和触摸事件的传递过程一样吗
记住 先触发响应者事件,接着触发触摸事件,也就是先寻找命中者(子->父)!!!!
看别人的博客的时候就在想他们是不是一个过程
实际上,触摸事件的传递确实是从父控件到子控件,而响应者链的传递是从子控件到父控件,两者的传递方向是相反的
下面是触摸事件的传递过程:
- 用户触摸屏幕,系统将创建一个 UITouch 对象,并将其加入到一个 UIEvent 对象中。
- UIEvent 对象传递给 UIApplication 对象,由 UIApplication 对象将事件分派给应用程序的 UIWindow。
- UIWindow 对象将事件分派给合适的视图控制器的视图层次结构。事件会从根视图开始沿着视图层次结构向下传递,直到找到合适的视图来响应事件。
- 视图响应事件,并将事件传递给其父视图,直到事件到达视图层次结构的最上层视图。
- 如果事件没有被任何视图对象处理,则系统将发送一个 cancel 事件,表示触摸事件已被取消。
而响应者链的传递过程则是从子控件到父控件,如下:
- 视图层次结构中的子控件可以成为响应者对象,当用户与子控件交互时,该控件将成为响应者对象。
- 当响应者对象无法处理事件时,它将将事件发送给其父控件,并在响应者链中继续向上传递。
- 如果父控件也无法处理事件,则将事件发送到祖先控件,直到到达视图层次结构中的最上层控件。
- 如果没有控件能够处理事件,则事件将被丢弃。
响应规则定义区别
记住 先触发响应者事件,接着触发触摸事件,也就是先寻找命中者!!!!!刚开始一直没搞懂,现在明白了。
触摸事件传递过程是从父控件到子控件,即由UIApplication将事件发送到最顶层的控件,然后由这个控件向下逐级传递事件,直到找到最合适的处理者为止。在传递过程中,如果一个控件能够响应事件,那么就会处理事件并结束传递;如果不能响应事件,那么就会将事件传递给下一个响应者。
而响应者链的传递过程则是从子控件到父控件。当一个控件接收到事件后,它首先会将事件交给自己的响应者对象处理,然后再交给它的父控件的响应者对象处理,直到到达最顶层的响应者对象为止。在传递过程中,如果一个响应者对象能够响应事件,那么就会处理事件并结束传递;如果不能响应事件,那么就会将事件传递给它的父响应者对象。
因此,虽然两个传递过程都涉及到父子控件之间的传递,但它们的传递顺序和目的不同。触摸事件传递过程主要是为了找到最合适的控件来处理事件,而响应者链传递过程则是为了让控件的父子关系中的响应者对象能够逐级处理事件。
过程区别
响应者链的事件传递过程:
- 如果 view 的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图;
- 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给 window 对象进行处理;
- 如果 window 对象也不处理,则其将事件或消息传递给 UIApplication 对象;
- 如果 UIApplication 也不能处理该事件或消息,则将其丢弃。
触摸事件处理的详细过程:
- 用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件;
- 找到最合适的视图控件后,就会调用控件的 touches 方法来作具体的事件处理;
- 这些 touches 方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理。响应者链条就是由多个响应者对象连接起来的链条,能很清楚地看见每个响应者之间的关系,并且可以让一个事件多个对象处理:
如何判断上一个响应者:
- 如果当前这个 view 是控制器的 view,那么控制器就是上一个响应者;
- 如果当前这个 view 不是控制器的 view,那么父控件就是上一个响应者。
事件传递的完整过程:
- 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件;
- 调用最合适控件的 touches…. 方法;
- 如果调用 [super touches….] 就会将事件顺着响应者链条往上传递,传递给上一个响应者;
- 接着就会调用上一个响应者的 touches…. 方法。
总结
iOS的响应者链机制的步骤是先通过hitTest和PointSide方法找到合适的控件-Initial View如果这个响应者能够响应
则进行
Initial View -> View Controller(如果存在) -> superview -> · ·· -> rootView -> UIWindow -> UIApplication
如果一个View有一个视图控制器(View Controller),它的下一个响应者是这个视图控制器,紧接着才是它的父视图(Super View),如果一直到Root View都没有处理这个事件,事件会传递到UIWindow(iOS中有一个单例Window),此时Window如果也没有处理事件,便进入UIApplication,UIApplication是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环
多看些博客才搞懂了什么是真正的响应过程,有的博客讲了一整篇也没讲清触摸事件和响应者链的区别联系,触摸事件就是基于响应者链的事件。推荐看道哥的iOS开发-响应者链Responder Chain】