【iOS】—— 响应者链和事件传递链

【iOS】—— 响应者链和事件传递链

  • 响应者连和事件传递链
    • 概念
      • 响应者
      • 响应链事件
      • 响应者
      • 响应链事件
      • iOS中的事件类型:
      • UIKit继承图
        • UIRseponder
        • UITouch
        • UIEvent
  • 事件的产生与传递
    • 传递链
    • 传递过程
  • 总结

响应者连和事件传递链

概念

响应者

  • 由离用户最近的view向系统传递。
  • initial view –> super view –> …… –> view controller –> window –> Application –> AppDelegate

响应链事件

  • 由系统向离用户最近的view传递。
  • UIKit –> active app’s event queue –> window –> root view –> …… –> lowest view
    在这里插入图片描述

响应者

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

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

响应链事件

在这里插入图片描述

iOS中的事件类型:

  • 触摸事件(手机在屏幕上触摸)
  • 加速计事件(手机摇一摇)
  • 远程遥控事件(遥控器控制)

UIKit继承图

在这里插入图片描述
通过继承图我们能知道,我们平时在使用的UI大多数都是继承自UIResponder的,只有继承自UIResponder的对象才能接收并处理事件,我们把这类对象称为“响应者”。就像UIApplication,UIViewController,UIView都继承自UIResponder,因此他们都可以接收处理事件。

UIRseponder

UIResponder中提供了三种处理事件的方法(触摸事件、加速计事件、远程控制事件),所以我们才能在UI中实现各种点击事件:

// 触摸事件
// 开始接触屏幕,就会调用一次
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
// 手指开始移动就会调用(这个方法会频繁的调用,其实一接触屏幕就会多次调用)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
// 手指离开屏幕时,调用一次
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
// 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,或者view上面添加手势时,系统会自动调用view的下面方法
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

// 加速计事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

// 远程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;

通过这三种处理事件的方法就可以知道事件的整个过程,我们平时经常使用的就是触摸事件,其中有两个参数,UITouch和UIEvent。

UITouch

当你用一根手指触摸屏幕时,会创建一个与之关联的UITouch对象,一个UITouch对象对应一根手指。 在事件中可以根据NSSet中UITouch对象的数量得出此次触摸事件是单指触摸还是双指多指等等。

属性

// 触摸产生时所处的窗口
@property(nonatomic, readonly, retain) UIWindow *window;
// 触摸产生时所处的视图
@property(nonatomic, readonly, retain) UIView *view;
// 短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
@property(nonatomic, readonly) NSUInteger tapCount;
// 记录了触摸事件产生或变化时的时间,单位是秒
@property(nonatomic, readonly) NSTimeInterval timestamp;
// 当前触摸事件所处的状态
@property(nonatomic, readonly) UITouchPhase phase;

UITouch的两个方法(可用于view的拖拽)

- (CGPoint)locationInView:(UIView *)view;
/*
  返回值表示触摸在view上的位置
  这里返回的位置是针对传入的view的坐标系(以view的左上角为原点(0, 0))
  调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置
*/

// 该方法记录了前一个触摸点的位置
- (CGPoint)previousLocationInView:(UIView *)view;

UIEvent

每产生一个事件,就会产生一个UIEvent事件,UIEvent称为事件对象,记录事件产生的时刻和类型等等。
UIEvent几个重要属性:

// 事件类型
@property(nonatomic, readonly) UIEventType type;
@property(nonatomic, readonly) UIEventSubtype subtype;
// 事件产生的时间
@property(nonatomic, readonly) NSTimeInterval timestamp;

事件的产生与传递

传递链

UIApplication传递事件到当前Window是明确的(即一定会的),接下来就是从Window开始找最佳响应视图,此过程有两个重要的方法:

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // 递归调用的事件,从最底层开始,一直往上找,直到找到一个最上层的能响应的视图就返回该视图
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // 判断点击区域是否在该视图的范围中,不在该视图范围中就结束递归,返回nil

传递过程

  • 1.发生触摸事件后,压力转为电信号,系统将产生UIEvent事件,记录事件产生的时间和类型。
  • 2.系统会将该事件添加到UIApplication管理的事件队列中。
  • 3.UIApplication将事件队列中的第一个事件分发给UIWindow,这时就会调用UIWindow的hitTest:withEvent: 方法。
  • 4.当前 window/视图 调用hitTest:withEvent:方法,hitTest:withEvent:方法内部会通过以下条件判断window/视图能否能响应事件,以下判断条件都是不能响应事件的:
    • 不允许交互:userInteractionEnabled=NO
    • 隐藏:hidden = YES
    • 透明度:alpha < 0.01,alpha小于0.01为全透明
  • 5.如果能响应,该函数又会调用pointInside方法判断当前触摸点是不是在视图范围内,不在视图范围内也是不会响应的。
  • 6.如果在 window/视图 范围内,开始反向遍历 window/视图 的子视图列表subviews,遍历的同时会调用subviews中每个子视图的hitTest:withEvent:方法,判断逻辑和上面的一样,直到找到离用户最近的、能响应事件的视图。
  • 4.5.6过程会递归判断,直到找到最外层合适的view,最后返回的view就是最佳响应视图。

hitTest:withEvent:方法的可能实现

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha) return nil;     
    // 2. 判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    // 3.从后往前遍历自己的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        // 把当前控件上的坐标系转换成子控件上的坐标系
        CGPoint childP = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) { // 直到寻找到最合适的view
            return fitView;
        }
    }
    // 循环结束,表示没有比自己更合适的view
    return self;
}

  • 查找结束,返回最终的view,UIApplication会调用UIWindow的sendEvent,从而触发对应的响应方法,如果我们在UIWindow中重写sendEvent而不调用super的实现,所有的点击事件都不会触发,因为事件是从最底层传递上来的,你切断了最底层的传递,肯定就无法响应了
  • 实际调用hitTest的过程,系统为了找到精准的触摸点会多次调用hitTest方法
  • 如果重写hitTest返回self,传递过程就会终止,那么当前view就是最合适的view;返回nil,传递也会终止,父视图superView就是最合适的view
  • 如果遍历subviews的过程都没找到合适的view,那么subviews中的子view的hitTest方法都会被调用一次
  • hitTest方法会调用pointInside判断当前视图是否在点击区域,所以超出父视图边界的控件无法响应事件
  • 同一个view上的两个子视图有重叠部分,后加入的视图会被加入到事件传递链
  • 在打印视图层级结构中部分视图执行hitTest和pointInSide方法中可以看到,viewController并没有执行这两个方法。所以传递链中没有viewController,因为viewController本身不具有大小的概念。而响应链中有viewController,因为viewController继承UIResponder。

响应链

当找到最合适的响应者之后,便会调用控件相应的touch方法来作具体处理,然而这些方法默认都是不做处理的,但是我们要是想让该响应者响应该事件就可以重写一开始说的那几个响应事件方法,并且我们也可以在重写touch方法中加入[super touch],使多个响应者同时响应同一事件。如果我们对响应事件的方法不做处理那么将该事件随着响应者链条往回传递,交给上一个响应者来处理(即调用super的touch方法),直到找到一个能响应该事件的响应者。

响应过程

  • 1.通过hitTest返回的view为当前事件的第一响应者,nextResponder为上一个响应者
  • 2.如果当前view默认不去重写响应事件方法,或者重写调用了父类的响应事件方法,响应就会沿着响应者链向上传递(上一个响应者一般是superView,可以通过nextResponder属性获取上一个响应者)
  • 3.如果上一个响应者是viewController,由viewController的view处理,如果view本身没处理,则传递给viewController本身
  • 4.重复上述过程,直到传递到window,window如果也不能处理,则传递到UIApplication,如果UIApplication的delegate继承自UIResponder,则交给delegate处理,如果delegate也不处理最后丢弃。

完整的触摸过程
一次完整的触摸过程,会经历 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 对象会将该事件发送给响应者链中的下一个响应者。如果没有下一个响应者,则该事件的响应过程结束。

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

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

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

相关文章

【CGAL】Region_Growing检测圆柱,保存结果并输出圆柱体参数

目录 说明代码展示结果展示问题说明 说明 这篇博客以代码为主&#xff0c;使用CGAL中的region growing方法检测圆柱体。将不同的圆柱按不同颜色保存&#xff0c;并输出圆柱体的中心坐标、轴方向以及半径。 region growing的具体思想网上的文章已经有很多&#xff0c;可以参考这…

安装mysql5.7,然后安装mysql workbench进行管理。

1、下载mysql5.7 MySQL :: Download MySQL Installer 这个是32位的安装版 MySQL :: Download MySQL Community Server (Archived Versions) 这个是zip版 这里我下载的是zip版本。 下载完成后&#xff0c;解压缩&#xff0c;然后新建文件my.ini进行配制。 my.ini [mysqld]…

Django DetailView视图

Django的DetailView是一个用于显示单个对象详情的视图。下面是一个使用DetailView来显示单个书籍详情的例子。 1&#xff0c;添加视图 Test/app3/views.py from django.shortcuts import render# Create your views here. from django.views.generic import ListView from .m…

数据结构预备知识(Java):包装类泛型

1、包装类 1.1 包装类 在Java中&#xff0c;每一个基本数据类型都有一个对应的包装类&#xff1a; 在SE的学习中我们已有过简单了解。 我们可以注意到&#xff0c;除了int类型的包装类为Integer&#xff0c;char类型的包装类为Character外&#xff0c;其余基本类型的包装类均…

wordpress主题开发

科普一&#xff1a;wordpress 是一套用 php 这个语言写的CMS后台管理系统&#xff0c;即我们大家的 wordpress 网站后台是一样的&#xff0c;能体现我们网站外观不同的地方就在于wordpress主题&#xff08;即皮肤&#xff09;&#xff0c;而这个主题的基本构成是 htmlcssjavasc…

JMH309【亲测】典藏3D魔幻端游【剑踪3DⅢ】GM工具+开区合区工具+PC客户端+配置修改教程+Win一键服务端+详细外网视频教程

资源介绍&#xff1a; 经典不错的一款端游 GM工具开区合区工具PC客户端配置修改教程Win一键服务端详细外网视频教程 资源截图&#xff1a; 下载地址

任务倒计时App

设计背景 在某一阶段可能需要给自己设置长期任务&#xff0c;比如找工作、考研等&#xff0c;需要一个单纯的任务计时工具&#xff0c;设置完任务的目标时间后&#xff0c;每次打开App时都能直接看到最新的剩余时间 设计步骤 1. 写java源码 由于需要界面显示&#xff0c;需…

使用MySQL

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 1 下载安装MySQL MySQL是一款开源的数据库软件&#xff0c;由于其免费特性得到了全世界用户的喜爱&#xff0c;是目前使用人数最多的数据库。下面将…

Unity【入门】重要组件和API

Unity重要组件和API 文章目录 1、最小单位GameObject1、成员变量2、静态方法1、代码创建Unity自带几何体 CreatePrimitive2、查找对象3、实例化对象&#xff08;克隆对象&#xff09;的方法4、删除对象的方法5、切换场景不移除 3、成员方法1、创建空物体2、为对象动态添加脚本(…

基数排序详解

目录 一、桶排序思想 1.1 什么是桶排序 1.2 桶排序的步骤 二、基数排序思想 2.1 什么是基数排序 2.2 实现方式 2.3 图解 三、代码思路 3.1 前置工作 3.2 映射 3.3 排序 四、C语言源码 一、桶排序思想 1.1 什么是桶排序 桶排序(Bucket sort)是一种排序算法&#xff…

网络安全课程开发

我们为卡巴斯基实验室开发了一个交钥匙教育门户网站&#xff0c;并为其开设了网络安全课程。在资源上&#xff0c;你可以熟悉课程的理论部分-观看视频或阅读插图文本版本&#xff0c;然后通过回答问题来验证你的知识。通过最终测试后&#xff0c;用户将获得证书。 对于这个项目…

Pythone 程序打包成 exe

1.安装pyinstaller # 安装 pip install pyinstaller # 查看版本 pyinstaller -v2.更新pyinstaller 版本 # 更新 pip install --upgrade pyinstaller # 查看版本 pyinstaller -v3.切换到 py文件所在目录 #切换到.py所在的目录 E: cd cd E:\x-svn_x-local\04PythoneProjects\A…

平安养老险陕西分公司荣获“2021-2023年乡村振兴‘三村工程’先进机构”

5月27日&#xff0c;中国平安成立36周年司庆暨三省推广启动大会顺利召开。会上&#xff0c;平安养老险陕西分公司获“2021-2023年乡村振兴‘三村工程’先进机构”荣誉表彰。 过去三年间&#xff0c;平安养老险陕西分公司始终坚持金融为民&#xff0c;在平安集团、平安养老险的指…

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【08】【商品服务】Object划分_批量删除

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【08】【商品服务】Object划分_批量删除 Object划分批量删除/添加参考 Object划分 数据库中对于一张表的数据&#xff0c;由于拥有隐私字段、多余字段、字段过少等原因&#xff0c;不应该直…

33 _ 跨站脚本攻击(XSS):为什么Cookie中有HttpOnly属性?

通过上篇文章的介绍&#xff0c;我们知道了同源策略可以隔离各个站点之间的DOM交互、页面数据和网络通信&#xff0c;虽然严格的同源策略会带来更多的安全&#xff0c;但是也束缚了Web。这就需要在安全和自由之间找到一个平衡点&#xff0c;所以我们默认页面中可以引用任意第三…

js之简单轮播图

今天给大家封装一个简单的轮播图,可以点击下一张上一张以及自动轮播 <!DOCTYPE html> <html><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>走马…

基于蚁群算法的二维路径规划算法(matlab)

微♥关注“电击小子程高兴的MATLAB小屋”获得资料 一、理论基础 1、路径规划算法 路径规划算法是指在有障碍物的工作环境中寻找一条从起点到终点、无碰撞地绕过所有障碍物的运动路径。路径规划算法较多&#xff0c;大体上可分为全局路径规划算法和局部路径规划算法两大类。其…

Neo4j 桌面版打不开踩坑贴

真的踩坑。。。没有人告诉我为啥桌面版和社区版不能一起下啊&#xff01;&#xff01; 我是先下载了社区版之后再下载的桌面版&#xff0c;结果桌面版界面一直打不开。 尝试了网上多种办法都没效果&#xff0c;好多都是说jdk不兼容导致无法打开&#xff0c;让我从JDK 17 ->…

跨域、JSONP、CORS、Spring、Spring Security解决方案

概述 JavaScript出于安全方面的考虑&#xff0c;不允许跨域调用其他页面的对象。跨域是浏览器&#xff08;如Chrome浏览器基于JS V8引擎&#xff0c;可以简单理解为JS解释器&#xff09;的一种同源安全策略&#xff0c;是浏览器单方面限制脚本的跨域访问。因此&#xff0c;仅有…

python使用wkhtmltopdf将html字符串保存pdf,解决出现方框的问题

出现的问题: 解决办法: <html> <head><meta charset="UTF-8"/> </head> <style> * {font-family: Arial,SimSun !important; } </style> </html>在html字符串前面加上上面代码,意思是设置字体编码和样式 html示例:…