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

news2024/9/21 4:27:02

事件传递

事件传递流程

  • 发生触摸事件后,系统会将该事件封装成UIEvent对象加入到一个由UIApplication管理的事件队列

  • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)

  • 主窗口会调用hitTest:withEvent: 方法沿着视图层次结构从上到下进行传递最后在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步

  • 找到合适的视图控件后,就会调用视图控件的touches方法(touchesBegan、touchesMoved、touchedEnded)来作具体的事件处理

触摸事件的传递是从父控件传递到子控件

也就是UIApplication->window->寻找处理事件最合适的view

触摸事件的传递是从父控件传递到子控件,如果父控件不能接受触摸事件,那么子控件就不可能接收到触摸事件

img

如何找到最合适的控件来处理事件?

  • 自己是否能接收触摸事件?

  • 触摸点是否在自己身上?

  • 从后往前遍历子控件,重复前面的两个步骤

  • 如果没有符合条件的子控件,那么就自己最适合处理

UIView不接收触摸事件的三种情况:

  • userInteractionEnabled = NO隐藏

  • hidden = YES;

  • 透明:alpha = 0.0 ~ 0.01;

通过pointInside:withEvent 方法判断触摸点是否在自己身上。返回NO则不在自己身上,那就不再遍历子控件,返回YES,代表在自己身上,那就继续遍历子控件,从后往前遍历子控件,重复前面两个步骤如果没有符合条件的子控件,那么自己就是最适合处理的控件找到“最合适” 接收的控件后,调用控件touchesBegan,touchesMoved,touchedEnded的方法。

事件传递示例

img

  • 点击了绿色的view:UIApplication -> UIWindow -> 白色 -> 绿色

  • 点击了蓝色的view:UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色

  • 点击了黄色的view:UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色 -> 黄色

寻找最合适的控件底层剖析

这里用到了两个重要的方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

hitTest:withEvent

只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法

为了寻找并返回最合适的view(能够响应事件的那个最合适的view)

下面是其实现逻辑:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1.判断下窗口能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    // 2.判断下点在不在窗口上
    // 不在窗口上
    if ([self pointInside:point withEvent:event] == NO) return nil;
    // 3.从后往前遍历子控件数组
    int count = (int)self.subviews.count;
    for (int 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;
        }
     }
     // 4.没有找到更合适的view,也就是没有比自己更合适的view
     return self;
 }

  • 首先判断下窗口能否接收事件

  • 接着调用当前视图的pointInside:withEvent: 方法判断触摸点是否在当前视图内

  • 若返回NO,则hitTest:withEvent: 返回 nil。

  • 若返回YES,则向当前视图的所有子视图发送hitTest:withEvent: 消息,所有子视图的遍历顺序是从最顶视图一直到最低层视图,即从 subviews 数组的末尾向前遍历,直到有子视图返回非空对象,或者全部子视图遍历完毕。

  • 若第一次有子视图返回非空对象,则hitTest:withEvent:返回此对象,处理结束。

  • 若所有子视图都返回空,则hitTest:withEvent:返回自身

不管这个控件能不能处理事件,也不管触摸点在不在这个控件上,事件都会先传递给这个控件,随后再调用hitTest:withEvent:方法

如果hitTest:withEvent:方法中返回nil,那么调用该方法的控件本身和其子控件都不是最合适的view,也就是在自己身上没有找到更合适的view。那么最合适的view就是该控件的父控件。

pointInside:withEvent

判断点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。

事件响应

事件响应流程

  • 如果找到最合适的控件来处理调用最合适的控件的touches…(touchesBegan、touchesMoved、touchedEnded)方法。

  • 如果调用了[super touch…],就会将事件顺着响应者链往上传递,传给上一个响应者,接着上一个响应者就会调用touches…方法。

  • 如果没有找到合适的控件来处理事件,则将事件传回来窗口,窗口不处理事件,将事件传给 UIApplication。如果 UIApplication 不能处理事件,则将其丢弃。

img

  • 系统首先检查当前触摸到的视图是否响应事件,如果响应事件传递结束,否则转步骤2

  • 系统检查当前触摸到的视图的控制器,如果控制器响应则事件传递结束;如果该视图没有控制器或者控制器不响应该事件,则转步骤3

  • 系统检查父视图,再检查父视图的控制器,以此类推

  • 最后,如果最顶层的视图/控制器也不响应则交给window

响应者

在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件,称之为“响应者对象”。

UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。

UIResponder提供了我们平时最常用的touchesBegan/touchesMoved/touchesEnded方法。此外还有如下几个属性比较重要:

  • isFirstResponder:判断该View是否为第一响应者。

  • canBecomeFirstResponder:判断该View是否可以成为第一响应者。

  • becomeFirstResponder:使该View成为第一响应者。

  • resignFirstResponder:取消View的第一响应者。

第一响应者和最佳响应者
  1. 第一响应者 (First Responder):

    • 第一响应者是指当前能够响应某个事件的第一个对象。
    • 通常情况下,当某个事件发生时,该事件首先被传递到第一响应者。
    • 第一响应者通常是用户当前正在交互的视图,比如用户正在编辑的 UITextField或者点击的 UIButton

    事件传递的目的就是为了让我们找到第一响应者

    如何判断第一响应者:

    1. 能够响应触摸事件
    2. 触摸点在自己身上
    3. 没有任何子视图,或是所有子视图都不在触摸点上
  2. 最佳响应者 (Best Responder):

    • 最佳响应者是指在响应者链上最适合处理某个事件的对象。
    • 当第一响应者无法完全处理某个事件时,该事件会沿着响应者链向上传递,直到找到最佳响应者。
    • 最佳响应者通常是能够最完整地处理该事件的对象,比如包含第一响应者的视图控制器。

一般来说,最佳响应者往往是包含当前第一响应者的视图控制器。

什么是上一个响应者

如果当前这个view是控制器的view,那么控制器就是上一个响应者;
如果当前这个view不是控制器的view,那么父控件就是上一个响应者。

响应者链条是什么

它是一种事件处理机制,由多个响应者对象连接起来的层次结构,使得事件可以沿着这些对象进行传递。利用响应者链条我们可以通过调用touches的super 方法,让多个响应者同时响应该事件。

如何做到一个事件多个对象处理

因为系统默认做法是把事件上抛给父控件,所以可以通过重写自己的touches方法和父控件的touches方法来达到一个事件多个对象处理的目的

iOS中的各种事件

iOS中的事件可以分为三种类型:

  • 触摸事件
  • 加速计事件
  • 远程控制事件

img

触摸事件(UITouch)

保存着跟手指相关的信息,比如触摸的位置、时间、阶段。 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置。 当手指离开屏幕时,系统会销毁相应的UITouch对象。

UITouch的常用属性和方法

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

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

UIEvent

UIEvent:称为事件对象,记录事件产生的时刻和类型。 每产生一个事件,就会产生一个UIEvent对象。 UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)。

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

触摸过程

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

4个触摸事件处理方法中,都有NSSet *touchesUIEvent *event两个参数。

当用户用手指触摸屏幕时,会创建一个与手指相关联的UITouch对象。一根手指对应一个UITouch对象。

  • 一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数。
  • 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象。
  • 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象。
  • 根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸。

touches中存放的都是UITouch对象

UIGestureRecognizer(手势识别器)

利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势。 UIGestureRecognizer是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势 UITapGestureRecognizer(敲击) UIPinchGestureRecognizer(捏合,用于缩放) UIPanGestureRecognizer(拖拽) UISwipeGestureRecognizer(轻扫) UIRotationGestureRecognizer(旋转) UILongPressGestureRecognizer(长按)

typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    // 没有触摸事件发生,所有手势识别的默认状态
    UIGestureRecognizerStatePossible,
    // 一个手势已经开始但尚未改变或者完成时
    UIGestureRecognizerStateBegan,
    // 手势状态改变
    UIGestureRecognizerStateChanged,
    // 手势完成
    UIGestureRecognizerStateEnded,
    // 手势取消,恢复至Possible状态
    UIGestureRecognizerStateCancelled, 
    // 手势失败,恢复至Possible状态
    UIGestureRecognizerStateFailed,
    // 识别到手势识别
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};

总结

传递链:有系统向最上层view传递,Application -> window -> root view -> … -> first view

响应连:由最基础的view向系统传递,first view -> super view -> … -> view controller -> window -> Application -> AppDelegate

穿透控件:

如果我们不想让某个视图响应事件,只需要重载 PointInside:withEvent:方法,让此方法返回NO就行了.

若是view上有view1,view1上有view2,点击view2,view2自己响应,点击view1,view1不响应,只有view响应,也就是隔层传递

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么你还不懂keepalived高可用负载均衡架构?

1、部署keepalived 1.1、keepalived简介 vrrp 协议的软件实现,原生设计目的为了高可用 ipvs 服务 官网: http://keepalived.org/ 功能: 基于 vrrp 协议完成地址流动 为 vip 地址所在的节点生成 ipvs 规则 ( 在配置文件中预先定义 ) 为 ipvs 集群的各 RS 做健康状态检测 基…

Linux 操作系统 --- 信号

序言 在本篇内容中&#xff0c;将为大家介绍在操作系统中的一个重要的机制 — 信号。大家可能感到疑惑&#xff0c;好像我在使用 Linux 的过程中并没有接触过信号&#xff0c;这是啥呀&#xff1f;其实我们经常遇到过&#xff0c;当我们运行的进程当进程尝试访问非法内存地址时…

NC 数组中只出现一次的两个数字

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 一个整型数组…

Apache--简介与基本使用

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、Apache简介 Apache HTTP Server&#xff08;在Red Hat发行版中俗称Apache或httpd&#xff09;是由Apache Software Foundation在Apache License…

windows bat脚本基础指令详解

pause暂停批处理的执行并在屏幕上显示"请按任意键继续…"echo显示指令&#xff0c;会把需要显示的内容展示出来。echo off在此语句后所有运行的命令都不显示命令行本身&#xff0c;但是本身的指令是会显示出来的。不显示本行命令行call调用另一个批处理文件&#xff…

Ricardo Milos

目录 一、题目 二、思路 三、payload 四、思考与总结 一、题目 <!-- Challenge --> <form id"ricardo" method"GET"><input name"milos" type"text" class"form-control" placeholder"True" va…

【C++STL详解(十一)】map/set/multimap/multiset的介绍与使用

目录 一、关联式容器 二、键值对 三、set 介绍 简单使用 1.构造 2.相关迭代器 3.容量 4.修改 四、multiset 五、map 介绍 使用 1.定义的方式 2.迭代器相关 3.容量与operator【】(重点) 4.修改 小总结&#xff1a; 六、multimap 一、关联式容器 在CSTL中…

【学习笔记】A2X通信的协议(十三)- 消息功能定义和内容

目录 11 消息功能定义和内容 11.1 概述 11.2 通过PC5信令消息进行的A2X通信 11.2.1 A2X直接链路建立请求 11.2.1.1 消息定义 11.2.1.2 目标用户信息 11.2.1.3 密钥建立信息容器 11.2.1.4 Nonce_1 11.2.1.5 KNRP-sess ID的最高有效位&#xff08;MSB&#xff09; 11.2…

替代 SMR 算法!两步孟德尔随机化方法 TWMR 与 revTWMR 整合xQTL+GWAS数据分析基因表达与疾病的关联

全基因组关联研究&#xff08;GWAS&#xff09;是研究大型队列中基因型与表型关系的重要工具。GWAS的已知局限性主要在于从与致病变异相关的连锁不平衡区域中识别生物学机制&#xff0c;而无法直接获得基因层面的表型关联。为了解决这个问题&#xff0c;基于转录组关联研究&…

C语言——函数专题

1.概念 在C语言中引入函数的概念&#xff0c;有些翻译为子程序。C语言中的函数就是一个完成某项特定任务的一小段代码&#xff0c;这个代码是有特殊的写法和调用方法的。一般我们可以分为两种函数&#xff1a;库函数和自定义函数。 2.库函数 C语言国际标准ANSIC规定了一些常…

Docker 容器运行时如何实现与宿主机的目录挂载

虽然容器启动前挂载目录相对简单,只需要在启动容器的时候通过-v 参数即可轻松实现,但对于已运行的容器进行目录挂载则稍显复杂。本教程将深入讲解如何在不中断容器运行的情况下,为已运行的Docker容器配置目录挂载,实现灵活的数据共享与持久化策略。 一、环境准备 为了本次实…

ffmpeg采用gpu加速增加水印

1.环境需要 系统 windows10 ffmpeg&#xff0c;ffprobe 字体文件 python3以上版本 2.环境配置 从官网上下载ffmpeg版本https://github.com/BtbN/FFmpeg-Builds/releases&#xff0c;这里我用的是这个&#xff0c;解压之后里面包含ffmpeg&#xff0c;ffprobe&#xff0c;f…

使用Kernel Memory进行RAG评估:AI助力企业知识管理新突破

在现代企业知识管理中&#xff0c;随着业务的不断发展和扩展&#xff0c;各种文档和数据呈现爆炸式增长。为了有效且高效地管理这些知识&#xff0c;企业通常会导入大量文档。然而&#xff0c;当涉及到对文档切片质量和回答准确度的判断时&#xff0c;传统的人工方法显得既费时…

复现dom破坏案例和靶场

目录 1.dom型xss平台 第一关 Ma Spaghet !: &#xff08;1&#xff09;&#xff1a;当get参数中存在somebody时&#xff0c;h2回显 &#xff08;2&#xff09;&#xff1a;当get参数中不存在somebody时&#xff0c;h2回显 第二关Jefff&#xff1a; 第三关&#xff1a;Ugan…

【前端面试】挖掘做过的nextJS项目(中)

https://blog.csdn.net/weixin_43342290/article/details/141170360?spm1001.2014.3001.5501文章浏览阅读105次。需求:快速搭建宣传官网1.适应pc、移动端2.基本的路由跳转3.页面渲染优化4.宣传的图片、视频资源的加载优化5.seo优化全栈react web应用、tailwind css原子工具的支…

动态规划篇--代码随想录算法训练营第三十二天|343. 整数拆分,96.不同的二叉搜索树,01背包理论,01背包优化

343. 整数拆分 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 讲解视频&#xff1a; 动态规划&#xff0c;本题关键在于理解递推公式&#xff01;| LeetCode&#xff1a;343. 整数拆分 题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k …

c++47 二级指针

二级指针的输入和输出模型 指针的输入&#xff1a;主调函数分配内存 指针输出 &#xff1a;被调用函数分配内存 指针做输入第一种模型 #define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <stdio.h>// 二级指针做输…

《计算机组成原理》(第3版)第8章 CPU的结构和功能 复习笔记

第8章 CPU的结构和功能 一、CPU的结构 &#xff08;一&#xff09;CPU的含义 CPU实质包括运算器和控制器两大部分。 对于冯诺依曼结构的计算机而言&#xff0c;一旦程序进入存储器后&#xff0c;就可由计算机自动完成取指令和执行指令的任务&#xff0c;控制器就是专用于完成…

Python爬虫图片:从入门到精通

在数字化时代&#xff0c;图片作为信息传递的重要媒介之一&#xff0c;其获取和处理变得越来越重要。Python作为一种功能强大且易于学习的编程语言&#xff0c;非常适合用来编写爬虫程序&#xff0c;帮助我们自动化地从互联网上获取图片资源。本文将从基础到高级&#xff0c;详…

CTF密码学小结

感觉没啥好总结的啊 基础的永远是RSA、流密码、哈希、对称密码、古典密码那一套&#xff08;密码学上过课都会&#xff09;&#xff0c;其他的就是数论的一些技巧 似乎格密码也很流行&#xff0c;以及一些奇奇怪怪的性质利用也很多 1、random设置种子后随机的性质&#xff1a…