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

事件传递

事件传递流程

  • 发生触摸事件后,系统会将该事件封装成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) {// 如果能找到最合适的viewreturn fitView;}}// 4.没有找到更合适的view,也就是没有比自己更合适的viewreturn 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.rhkb.cn/news/403476.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP详解(一)报文详情/MSS/MTU

本文旨在介绍TCP的报文格式详情和传输层、链路层的字节数限制 1 TCP 协议的报文格式 TCP 报文段包括协议首部和数据两部分&#xff0c;协议首部的固定部分是 20 个字节&#xff0c;头部是固定部分&#xff0c;后面是选项部分。 1.1 端口号 16位源端口&#xff1a;发送方主机…

KDP数据平台:以实战案例验证技术领先力

本文由智领云 LeetTools 工具自动生成 申请试用&#xff1a; https://www.leettools.com/feedback/ 在当今快速发展的技术环境中&#xff0c;数据平台的选择对企业的数字化转型和业务发展至关重要。智领云开源KDP&#xff08;Kubernetes Data Platform&#xff09;在数据处理和…

效果炫酷的3D翻转书特效WordPress主题模板MagicBook主题v1.19

正文&#xff1a; MagicBook是一款支持3D翻书特效的书籍WordPress主题。支持可视化页面搭建&#xff0c;3D菜单&#xff0c;完全自适应设计,WPML多语言支持。 这款主题一定会让你爱不释手。虽然他是英文的&#xff0c;但不可不承认的是&#xff0c;它优雅的设计会让你愿意花时…

无缝融入,即刻智能[二]:Dify-LLM平台(聊天智能助手、AI工作流)快速使用指南,42K+星标见证专属智能方案

无缝融入,即刻智能[二]:Dify-LLM平台(聊天智能助手、AI工作流)快速使用指南,42K+星标见证专属智能方案 1.快速创建应用 你可以通过 3 种方式在 Dify 的工作室内创建应用: 基于应用模板创建(新手推荐) 创建一个空白应用 通过 DSL 文件(本地 / 在线)创建应用 从模板创建…

13 定时器

13 定时器 1、定时1.1 硬件定时器的特性1.2 硬件定时器对应的中断处理函数所作的工作(了解)1.3 linux内核中跟时间相关的三个概念&#xff1a; 2、延时2.1.延时定义2.2 忙等待2.3.休眠等待2.4 等待队列机制2.4.1 介绍2.4.2 结论2.4.3 进程休眠和唤醒的编程步骤方法 1方法 2 3、…

关于uniapp使用izExif.js 插件问题

需求&#xff1a;1.APP获取图片的属性&#xff0c;得到经纬度信息&#xff0c;然后标注到图片上 我们采用izExif.js 插件&#xff0c;进行获取图片信息&#xff0c;在模拟器测试好好地&#xff0c;但是使用真机测试发现getImageData没有返回信息&#xff0c;去izExif.js源码查…

ubuntu中python 改为默认使用python3,pip改为默认使用pip3

一、安装pip和python&#xff08;有的话可跳过&#xff09; 更新软件源 sudo apt update !!!apt和apt-get apt apt-get、apt-cache 和 apt-config 中最常用命令选项的集合。 部分截图为apt-get&#xff0c;建议直接用apt 安装pip和python ubuntu 18.04和更高版本默认安…

字符串金额转换,字符串手机号屏蔽,身份证信息查看,敏感词替换

2135 在发票上面该写成零佰零拾零万贰仟壹佰叁拾伍元 我们用逆推法可以写成零零零贰壹叁伍->贰壹叁伍->2135 1.遍历获取到每一个数字&#xff0c;然后把大写放到数组里面&#xff0c;将数字当作索引&#xff0c;在数组里面查找大写 package stringdemo;import java.uti…

Jakarta Servlet 到 SpringMVC

Jakarta EE&#xff08;曾被称为Java EE&#xff09;是Java平台企业版&#xff08;Java Platform Enterprise Edition&#xff09;的下一代版本&#xff0c;它在Oracle将Java EE的开发和维护交给Eclipse Foundation后得以重生&#xff0c;并更名为Jakarta EE。Jakarta EE保留了…

Windows采用VS2019实现Open3D的C++应用

1、参考链接 https://blog.csdn.net/qq_31254435/article/details/137799739 但是&#xff0c;我的方法和上述链接不大一样&#xff0c;我是采用VS2019进行编译的&#xff0c;方便在Windows平台上验证各种算法。 2、创建一个VS2019的C Console工程 #include <iostream>…

MT1619 (A/B/C/D 15W-25W)快充电源主控芯片

MT1619 是一款快充电源主控芯片&#xff0c;MT1619内部集成了一颗高集成度、高性能的电流模式 PWM 控制器和一颗功率 MOSFET。MT1619适用于小于 30W 的开关电源。MT1619 具有恒功率功能&#xff0c;特别适用于 PD 充电器、电源适配器等中小功率的开关电源设备。极低的启动电流与…

windows下TortoiseSVN切换账号的方法

前言 在项目开始初期的时候大家会使用一个默认账号,后面会根据需要给每个人分配各自的个人账号,这个时候就需要重登陆新的svn账号,下面就是讲解下怎样在windows下修改登录TortoiseSVN的账号。 方法 1.首先在桌面右键&#xff0c;选择TortoiseSVN-settings 2.进入设置页面&a…

阿里云注册、认证、短信资质、签名、模板申请过程

一、帐号注册 输入“帐号密码注册”中的相关信息即可。 手机号是必须的&#xff0c;先确定好手机号。 正常的可以直接注册成功的。 二、实名认证 注册成功之后&#xff0c;就可以点击上述的“快速实名认证”。 这次选择的是“企业认证”。 有几种方式&#xff0c;如下&#x…

clamp靶机复现

靶机设置 设置靶机为NAT模式 靶机IP发现 nmap 192.168.112.0/24 靶机IP为192.168.112.143 目录扫描 dirsearch 192.168.112.143 访问浏览器 提示让我们扫描更多的目录 换个更大的字典&#xff0c;扫出来一个 /nt4stopc/ 目录 目录拼接 拼接 /nt4stopc/ 发现页面中有很多…

CeresPCL 岭回归拟合(曲线拟合)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 由于在使用最小二乘插值拟合时,会涉及到矩阵求逆的操作,但是如果这个矩阵接近于奇异时,那么拟合的结果就会与我们期望的结果存在较大差距,因此就有学者提出在最小二乘的误差函数中添加正则项,即: 这里我们也可…

【SpringBoot】SpringBoot框架的整体环境搭建和使用(整合Mybatis,Druid,Junit4,PageHelper,logback等)

目录 1.介绍 1.1 配置文件 1.2 目录结构 2.基于SpringBoot的SpringMVC 4.整合Mybatis 5.整合Druid连接池 6.整合Junit4 7.整合Logback 8.整合PageHelper 9.SpringBoot整合Thymeleaf ​编辑 【附录】springboot的pom.xml 1.介绍 Spring框架的优点是方便解耦、简化开…

Python -- GUI图形界面编程—GUI编程实例 博主也在持续学习中[ 持续更新中!!! 欢迎白嫖 也求粉啊啊啊~ ]

本文介绍了GUI的图形界面编程&#xff08;相关视频是哔站上的应该搜这个题目就能找到&#xff09;&#xff0c;文章还是很基础的&#xff0c;反正我是小白从0开始&#xff0c;主要的结构tinkter库、重要组件简介&#xff08;这个不用死记硬背 用的时候再说&#xff09;、Label&…

用于不平衡医疗数据分类的主动SMOTE

一、主动学习如何应用于不平衡数据的处理 首先&#xff0c;主动SMOTE不是像经典的SMOTE那样从训练集中随机选择一个样本作为生成合成样本的轴心点&#xff0c;而是通过不确定性和多样性采样来智能地进行样本选择&#xff0c;这是主动学习的两种技术。 在数据不平衡的情况下&…

Ubuntu上安装Redis的详细教程

1、安装redis 首先&#xff0c;访问Redis官网&#xff0c;点击首页的【Get Started】&#xff0c;然后点击Install Redis on Linux 安装 终端依次输入以下命令&#xff0c;如果过程中没有错误提示&#xff0c;则redis安装完成。 sudo apt install lsb-release curl gpg cu…

计算机科学速成课笔记

计算机速成课个人理解概要 1.计算机的本质 计算机的本质&#xff1a;极其简单的组件&#xff0c;经过一层层复杂的抽象&#xff0c;做出复杂的工作 2.控制电流的技术发展 继电器→真空管→晶体管 拥有了细微控制电流的手段 tip:早期机械计算机&#xff0c;被虫子(bug)卡住…