【iOS开发-响应者链Responder Chain】

文章目录

  • 0.0 前言
  • 1 响应者链(Responder Chain
      • 1.1 响应者
      • 1.2 响应链事件
      • 1.3 响应者对象
          • 1.3.1 常见的响应者对象
          • 1.3.3 UIResponder
      • 1.3 UITouch
          • 1.3.1 UITouch的属性
        • 1.3.2 UITouch的方法
      • 1.4 UIEvent
          • 1.4.2 获取touch
      • 1.5 完整的响应者链
          • 1.5.1寻找响应者的hitTest方法
          • 1.5.2寻找响应者的pointInside方法
      • 1.6 完整的触摸过程
          • 1.6.1 完整的触摸过程如何实现
          • 1.6.2 举例
      • 1.7 iOS的响应者链的传递过程和触摸事件的传递过程一样吗
          • 1.7.1 响应规则定义区别
          • 1.7.2 过程区别
  • 总结

0.0 前言

源码的学习是枯燥的,困难的。。。。所以打算看一些别的东西缓缓。。

因为手机的版本问题,我一直没办法进行真机测试。一些点击事件什么的都很模糊,都是用时电脑的光标点击完成的,在这个点击app实现功能的过程里面就涉及到了一些响应过程。在iOS里,被称为响应者链(Responder Chain

1 响应者链(Responder Chain

iOS响应者链是一种事件处理机制,它描述了在iOS系统中,当一个事件(比如用户点击屏幕或者发送手势)发生时,它将从触发事件的源头开始,按照预定义的顺序传递给各个视图或控件进行处理,直到最终被处理或被丢弃。

  • 从本质上讲,苹果设备响应事件的整个过程可以分为两个步骤:
    步骤1:寻找目标。在iOS视图层次结构中找到触摸事件的最终接受者;
    步骤2:事件响应。基于iOS响应者链(Responder Chain)处理触摸事件。

1.1 响应者

响应者链中的各个对象被称为“响应者”(responder)响应者链的根节点是UIApplication对象,所有的事件都从UIApplication对象开始传递。当事件发生时,UIApplication对象首先将其传递给当前显示在屏幕上的UIWindow对象,然后递归地向下传递给其子视图,依次传递给UIViewControllerUIView等响应者对象进行处理,直到事件被处理完毕或被丢弃。

在响应者链中,每个响应者对象都可以处理事件,也可以选择将事件传递给下一个响应者对象进行处理,或者直接丢弃事件。响应者链中的每个响应者对象都可以重写几个方法来处理事件,这些方法包括touchesBegan:withEvent:touchesMoved:withEvent:、touchesEnded:withEvent:等等。

响应者链的机制在iOS开发中非常重要,它确保了事件可以在正确的对象中得到处理,保证了iOS应用的用户交互和响应体验。

1.2 响应链事件

  • iOS 中的事件可分为:触摸事件(multitouch events)、加速计事件(accelerometer events)、远程控制事件(remote control events)。

事件表请添加图片描述
响应者链 Responder Chain概览
在这里插入图片描述

1.3 响应者对象

首先,在iOS里能够处理事件的对象被称为响应者对象,但是不是任何对象都能够处理响应者事件。

在iOS中,响应者对象是指能够响应用户事件并进行相关处理的对象,它们通常是UIView或其子类,也可以是UIViewController或其子类。响应者对象都实现了UIResponder协议,并提供了一些方法来响应用户事件,比如touchesBegan:withEvent:、touchesMoved:withEvent:、touchesEnded:withEvent:等方法

这里的实现UIResponder协议指UIApplication、UIViewController、UIView 都继承于 UIResponder。

1.3.1 常见的响应者对象
  • UIView:是iOS中最基本的用户界面元素,可以接收用户的触摸事件并进行相关的处理。
  • UIViewController:作为MVC模式中的控制器,可以响应用户的触摸事件,同时还可以管理一个或多个视图控制器。
  • UIWindow:是整个应用程序的窗口,它包含了一个或多个视图,并且是接收和处理触摸事件的最高层响应者对象。
  • UIGestureRecognizer:是iOS中专门用来处理手势事件的响应者对象,包括UITapGestureRecognizer、UIPanGestureRecognizer、UILongPressGestureRecognizer等等。
  • UIScrollView:是一个可以滚动的视图控件,它可以接收用户的触摸事件,并在触摸拖动时进行滚动。
  • UITableView:是iOS中常用的列表视图控件,它可以显示大量的数据,并且可以处理用户的滑动、点击等事件。
1.3.3 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 中定义的三个方法,用于处理设备运动事件。这些事件包括摇晃、倾斜、加速等设备运动。
// 当设备开始运动时,系统会调用该方法。在该方法中,开发者可以获取设备运动的方向和强度,并进行相应的处理。例如,可以在该方法中播放一段音乐或者进行一些动画效果,以响应设备的运动事件
- (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);

注意⚠️:这些方法可以被任何继承自 UIResponder 的对象重写,在自己的类中处理设备运动事件,并进行相应的响应。需要注意的是,只有在设备支持相关的运动事件时,这些方法才会被调用。

  • UIResponder远程控制事件
- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);

remoteControlReceivedWithEvent 是一个 iOS 中的 UIResponder 方法,用于接收远程控制事件。这些远程控制事件可以来自于耳机、锁屏界面和控制中心等,例如暂停音乐、切换歌曲等。

当接收到远程控制事件时,系统会将事件封装成一个 UIEvent 对象,然后将该对象传递给当前响应者对象的 remoteControlReceivedWithEvent 方法。在该方法中,开发者可以获取 UIEvent 对象,并根据事件类型进行相应的处理。

需要注意的是,**只有当应用程序处于播放状态时,才会接收到远程控制事件。**如果应用程序没有进行播放,系统不会向应用程序发送远程控制事件。此外,在使用远程控制功能时,开发者还需要在应用程序的 Info.plist 文件中声明相应的背景模式。

1.3 UITouch

不得不说OC的编写者是一个极其聪明的人,什么事情都能想到。

  • 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的 UITouch 对象,一根手指对应一个 UITouch 对象。
  • UITouch 的作用:保存着跟手指相关的信息,比如触摸的位置、时间、阶段。当手指移动时,系统会更新同一个 UITouch 对象,使之能够一直保存该手指的触摸位置;当手指离开屏幕时,系统会销毁相应的 UITouch 对象。
  • 很强大:UITouch 对象在触摸事件的过程中会不断更新直到触摸事件结束。在触摸事件的过程中,系统会不断向响应链中的响应者发送触摸事件,并将触摸事件封装成 UIEvent 对象进行传递。当触摸事件结束时,系统会销毁相应的 UITouch 对象
1.3.1 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

1.3.2 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));

1.4 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;

1.4.2 获取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

coalescedTouchesForTouch:是UIEvent类中的一个方法,返回与指定的touch对象相关联的按时间排序的所有touch对象数组,这些对象代表指定的触摸事件。所谓按时间排序,就是根据每个 touch 对象的时间戳从早到晚排列。
这个方法主要用于处理手势识别过程中的touch事件,因为在手势识别过程中,系统可能会合并一些触摸事件(即coalesced touches),以减少事件数量并提高识别性能。而在手势识别过程中,我们需要知道所有的 touch 信息,包括合并过的 touch,所以可以使用该方法获取到所有按时间排序的 touch 对象数组。

- (nullable NSArray <UITouch *> *)coalescedTouchesForTouch:(UITouch *)touch API_AVAILABLE(ios(9.0));
  • predictedTouchesForTouch

所谓predict就是预测
predictedTouchesForTouch(_) 是 UIEvent 类的一个方法,用于获取未来几个周期中可能发生的触摸事件,以帮助在下一个周期中更准确地处理触摸事件。

在 iOS 中,由于触摸事件的处理通常需要花费一定的时间,因此在用户快速滑动或快速触摸时,可能会出现丢失触摸事件或处理不准确的情况。predictedTouchesForTouch(_) 方法可以在用户快速移动手指时提供额外的触摸事件预测,从而改善触摸事件的响应速度和精度。

具体而言,predictedTouchesForTouch(_) 方法可以返回一系列 UITouch 对象,这些对象描述了未来几个周期中可能发生的触摸事件**。这些事件是由系统根据当前触摸事件的历史记录和其他信息预测出来的,并不是实际发生的触摸事件。开发者可以利用这些预测事件来更准确地响应触摸事件,以提高应用程序的性能和响应速度。**

```objectivec
- (nullable NSArray <UITouch *> *)predictedTouchesForTouch:(UITouch *)touch API_AVAILABLE(ios(9.0));```


1.5 完整的响应者链

  • 从本质上讲,苹果设备响应事件的整个过程可以分为两个步骤:
    步骤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 等。
    • 如果第一响应者无法处理事件,事件会沿着响应者链向上传递,直到找到可以处理事件的对象为止。
    • 如果没有响应事件的对象,则事件会被丢弃。
1.5.1寻找响应者的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;
}
1.5.2寻找响应者的pointInside方法

pointInside:withEvent: 方法是 UIView 的方法,用于判断指定的点是否在当前视图内。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
  • 其中,point 表示需要检查的点坐标,event 表示与此事件相关联的事件对象。
  • 当调用该方法时,系统会首先判断该点是否在当前视图的 bounds 内,如果不在,直接返回 NO;否则继续判断是否在当前视图的子视图中,如果有子视图包含该点,则递归调用该子视图的 pointInside:withEvent: 方法,直到找到包含该点的最底层子视图。

最后,如果没有子视图包含该点,则当前视图就是最底层的视图,返回 YES,表示该点在当前视图内。

1.6 完整的触摸过程

一次完整的触摸过程,会经历 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

1.6.1 完整的触摸过程如何实现

一个完整的触摸事件流程通常包括以下几个步骤:

  • 手指触摸到屏幕,系统会创建一个与手指相关联的 UITouch 对象,并将其加入到系统中的事件队列中。
    系统会将该事件发送给当前 UIWindow 对象,即调用 UIWindow 对象的 touchesBegan(_:with:) 方法,并将该事件传递给子视图。
  • 从根视图开始,系统会通过递归调用 hitTest(_:with:) 方法,寻找响应该事件的视图。在每个视图中,系统都会调用 point(inside:with:) 方法,判断该视图是否包含该事件的触摸点。
  • 一旦找到了响应该事件的视图,系统会将该事件发送给该视图,即调用该视图的 touchesBegan(:with:) 方法。
    在该视图的 touchesBegan(
    :with:) 方法中,开发者可以对该事件做出相应的处理,比如更改视图的状态、更新视图的内容等。
  • 如果该事件需要传递给其它视图进行处理,开发者可以手动调用 next 方法,将该事件传递给下一个响应者。
  • 当手指离开屏幕时,系统会将一个 touch 对象的 phase 属性设置为 .ended,并将该 touch 对象从事件队列中移除。
  • 当前的 UIWindow 对象会将该事件发送给响应者链中的下一个响应者。如果没有下一个响应者,则该事件的响应过程结束。
1.6.2 举例
  • 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。
    UIImageView 的 userInteractionEnabled 默认就是 NO,因此 UIImageView 以及它的子控件默认是不能接收触摸事件的。

1.7 iOS的响应者链的传递过程和触摸事件的传递过程一样吗

记住 先触发响应者事件,接着触发触摸事件,也就是先寻找命中者(子->父)!!!!
看别人的博客的时候就在想他们是不是一个过程

实际上,触摸事件的传递确实是从父控件到子控件,而响应者链的传递是从子控件到父控件,两者的传递方向是相反的
下面是触摸事件的传递过程:

  • 用户触摸屏幕,系统将创建一个 UITouch 对象,并将其加入到一个 UIEvent 对象中。
  • UIEvent 对象传递给 UIApplication 对象,由 UIApplication 对象将事件分派给应用程序的 UIWindow。
  • UIWindow 对象将事件分派给合适的视图控制器的视图层次结构。事件会从根视图开始沿着视图层次结构向下传递,直到找到合适的视图来响应事件。
  • 视图响应事件,并将事件传递给其父视图,直到事件到达视图层次结构的最上层视图。
  • 如果事件没有被任何视图对象处理,则系统将发送一个 cancel 事件,表示触摸事件已被取消。

而响应者链的传递过程则是从子控件到父控件,如下:

  • 视图层次结构中的子控件可以成为响应者对象,当用户与子控件交互时,该控件将成为响应者对象。
  • 当响应者对象无法处理事件时,它将将事件发送给其父控件,并在响应者链中继续向上传递。
  • 如果父控件也无法处理事件,则将事件发送到祖先控件,直到到达视图层次结构中的最上层控件。
  • 如果没有控件能够处理事件,则事件将被丢弃。、
1.7.1 响应规则定义区别

记住 先触发响应者事件,接着触发触摸事件,也就是先寻找命中者迷🎯!!!!!刚开始一直没搞懂,现在明白了。

触摸事件传递过程是从父控件到子控件,即由UIApplication将事件发送到最顶层的控件,然后由这个控件向下逐级传递事件,直到找到最合适的处理者为止。在传递过程中,如果一个控件能够响应事件,那么就会处理事件并结束传递;如果不能响应事件,那么就会将事件传递给下一个响应者。

**而响应者链的传递过程则是从子控件到父控件。**当一个控件接收到事件后,它首先会将事件交给自己的响应者对象处理,然后再交给它的父控件的响应者对象处理,直到到达最顶层的响应者对象为止。在传递过程中,如果一个响应者对象能够响应事件,那么就会处理事件并结束传递;如果不能响应事件,那么就会将事件传递给它的父响应者对象。

因此,虽然两个传递过程都涉及到父子控件之间的传递,但它们的传递顺序和目的不同。触摸事件传递过程主要是为了找到最合适的控件来处理事件,而响应者链传递过程则是为了让控件的父子关系中的响应者对象能够逐级处理事件。

1.7.2 过程区别

响应者链的事件传递过程:

  • 如果 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,以结束整个循环

多看些博客才搞懂了什么是真正的响应过程,有的博客讲了一整篇也没讲清触摸事件和响应者链的区别联系,触摸事件就是基于响应者链的事件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/11780.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

excel在文本的固定位置插入字符、进行日期和时间的合并

1.excel在文本的固定位置插入字符 如上图&#xff0c;现在想要将其转化为日期格式&#xff08;比如2017/1/1&#xff09;&#xff0c;但是当设置单元格格式为日期时却显示出很多&#xff03;。我们可以通过在20170101中添加两个斜杠“/”来将其转化为2017/1/1。可以用replace函…

【分享】如何移除PDF密码?

相信不少小伙伴在工作的时候&#xff0c;经常会为了保证PDF文档的安全和私密而给文件设置“打开密码”&#xff0c;但如果后续需要频繁使用该文件&#xff0c;每次打开都要查找输入密码&#xff0c;就会使得工作效率大大降低耽误工作时间。那后续不需要设置保护了&#xff0c;要…

如何用ChatGPT写文章?只需要这3步,10倍提升写作效率

随着技术的不断进步和创新&#xff0c;我们的生活方式和工作方式也在不断变化。在日常工作中&#xff0c;越来越多的人使用人工智能和机器学习等技术提高效率减少时间成本。最近ChatGPT火出圈了&#xff0c;很多人通过使用ChatGPT提高了工作效率。那么&#xff0c;在写作领域&a…

【C++ 三】一维数组、二维数组

数组概述、一维数组、二维数组 文章目录数组概述、一维数组、二维数组前言1 数组1.1 概述2 一维数组2.1 一维数组定义方式2.2 一维数组数组名2.3 冒泡排序3 二维数组3.1 二维数组定义方式3.2 二维数组数组名总结前言 本文包含数组概述、一维数组、二维数组。 1 数组 1.1 概述…

Jina AI 创始人肖涵博士:揭秘 Auto-GPT 喧嚣背后的残酷真相

Auto-GPT 究竟是一个开创性的项目&#xff0c;还是一个被过度炒作的 AI 实验&#xff1f;本文为我们揭开了喧嚣背后的真相&#xff0c;并揭示了 Auto-GPT 不适合实际应用的生产局限性。 背景介绍 这两天&#xff0c;Auto-GPT&#xff0c;一款让最强语言模型 GPT-4 能够自主完成…

口令暴力破解--Ftp协议暴力破解与Ssh协议暴力破解

Ftp协议暴力破解 FTP服务检测 FTP服务 FTP是一种文件传输协议&#xff0c; FTP服务默认端口为21。利用FTP服务器可以在本地主机和远程主机间进行文件传输。当FTP没有配置好安全控制&#xff0c;如对登录的源地址及密码尝试次数做限制&#xff0c;那么就会存在暴力破解可能。…

计算机网络

文章目录数据传输过程及接收过程应用层传输层TCP/IP4层网络模型 1.应用层 2.传输层 3.网络层 4.数据链路层 数据传输过程及接收过程 用户A在聊天软件上输入hello world按下发送,发送给B 一: 传输 1.应用层:构建一个应用层的数据报文交给传输层 ** 2.传输层: 根据刚才传过来的…

java虚拟机反射机制

&#xff08;1&#xff09;Java虚拟机反射机制的定义&#xff1f; Java反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功…

MySQL事务隔离级别

一、概念说明 脏读&#xff1a;指的是读到了其他事务未提交的数据&#xff0c;未提交意味着这些数据可能会回滚&#xff0c;也就是可能最终不会存到数据库中&#xff0c;也就是不存在的数据。读到了并不一定最终存在的数据&#xff0c;这就是脏读。 可重复读&#xff1a;在一个…

【GPT4】微软 GPT-4 测试报告(3)GPT4 的编程能力

欢迎关注【youcans的GPT学习笔记】原创作品&#xff0c;火热更新中 微软 GPT-4 测试报告&#xff08;1&#xff09;总体介绍 微软 GPT-4 测试报告&#xff08;2&#xff09;多模态与跨学科能力 微软 GPT-4 测试报告&#xff08;3&#xff09;GPT4 的编程能力 【GPT4】微软 GPT-…

ChatGPT和GPT-4带你选笔记本电脑

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

(只需三步)如何用chatgpt自动生成思维导图

目录 chatgpt是可以生成思维导图的&#xff01;只需三步&#xff0c;非常简单&#xff01; 第一步&#xff1a;打开chatgpt&#xff0c;告诉它主题 第二步&#xff0c;完善思维导图 第三步&#xff1a;查看思维导图的效果 chatgpt是可以生成思维导图的&#xff01;只需三步&am…

vue2路由(下)

编程式路由导航 通过点击按钮实现push和replace俩种模式的跳转 实现&#xff1a;就是通过$router原型里面的方法 也能实现路由的跳转和后退&#xff0c;分别采用的是$router里面的black和forward方法 感觉就是BOM对象中的history对象里面的方法 正是前进&#xff0c;后是后…

C/C++|物联网开发入门+项目实战|嵌入式C语言高级|C语言常用关键字及运算符操作-学习笔记(8)

文章目录2-2: C语言常用关键字及运算符操作关键字参考&#xff1a; 麦子学院-嵌入式C语言高级2-2: C语言常用关键字及运算符操作 [重点] 掌握C语言的常用关键宇及其应用场景&#xff0c;使用技巧 关键字 编译器&#xff1a;预先定义了一定意义的字符串&#xff0c;32个。 s…

通讯录的实现(初级版本)

思维导图&#xff1a; 目录 思维导图&#xff1a; ​编辑 一&#xff0c;实现通讯录的第一步——实现逻辑 二&#xff0c;头文件内实现的内容 三&#xff0c;contect.c文件内的函数定义 3.1初始化通讯录:void InitContect(cotect*pc) 3.2:实现通讯录内容的增加&#xff…

Python的基础

这是我自己学习Python的三个星期的小总结&#xff0c;内容包含了规范、数据类型、函数、类和捕捉异常&#xff0c;做了一个简单的梳理&#xff0c;希望可以帮助到和我一样开始学习Python的小伙伴&#xff0c;也希望多多支持&#xff0c;相互进步&#xff0c;下面步入正题。 基…

【Python】Python中的列表,元组,字典

文章目录列表创建列表获取元素修改元素添加元素查找元素删除元素列表拼接遍历列表切片操作元组创建元组元组中的操作字典创建字典添加/修改元素删除元素查找字典的遍历合法的key类型列表 列表是一种批量保存数据的方式&#xff0c;列表使用[]表示 创建列表 创建两个空列表 …

Java对日开发成趋势?网友:找工作打开了新思路

近两年行业环境起起伏伏&#xff0c;企业降本增效&#xff0c;提高人才招聘的门槛&#xff0c;导致大家找工作时觉得越来越难&#xff0c;尤其是Java开发&#xff0c;主打的就是一个“卷”&#xff01; 不过行业变革&#xff0c;挑战与机遇并存。Java作为编程语言排行榜的常年第…

malloc hook进行内存泄漏检测

记录下使用malloc的hook形式&#xff0c;写个小的demo&#xff0c;并记录遇到的问题 1. 实现代码&#xff1a; CMakeLists.txt和相应的memory_leak.cpp文件 cmake_minimum_required(VERSION 3.14) project(demo)set(_SRCmemory_leak.cpp)add_library(memory_leak SHARED ${_S…

SpringCloud:ElasticSearch之索引库操作

ElasticSearch索引库就类似数据库表&#xff0c;mapping映射就类似表的结构。 我们要向ElasticSearch中存储数据&#xff0c;必须先创建“库”和“表”。 1.mapping映射属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; type&#xff1a;…
最新文章