iOS学习

  • 响应者链和事件传递链
    • 传递链:
      • hitTest:withEvent
      • **pointInside:withEvent**
    • 响应链
      • 第一响应者和最佳响应者
      • 触摸事件(UITouch)
    • UIGestureRecognizer(手势识别器)


响应者链和事件传递链

iOS事件的主要由:响应连和提交链构成。一般事件先通过提交链,提交下去。响应链,如果上层不能响应,那么一层通过响应链找到能响应的UIResponse

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

  • 交付链:有系统向最上层view交付,Application-> window-> root view-> … ->first view

iOS中只有继承了UIResponse的对象才能够接受处理事件。UIResponse是响应对象的基类,定义了处理上述各种事件的接口。常见的子类有:UIViewUIViewControllerUIApplicationUIApplicationDelegate

穿透控件:

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

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

与加速器,陀螺仪和磁力计相关的运动事件不遵循响应者连,Core Motion将这些事件直接传递给你指定的对象

传递链:

事件传递流程

  • 发生触摸事件后,系统会将该事件封装成UIEvent对象加入到一个由UIApplication管理的事件队列
  • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
  • 主窗口会调用hitTest:withEvent: 方法沿着视图层次结构从上到下进行传递最后在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步。
  • 找到合适的视图控件后,就会调用视图控件的touches方法(touchesBegan、touchesMoved、touchedEnded)来作具体的事件处理。

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

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

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

img

上文中说到:要找到最合适的控件来处理事件,那么,如何找到呢?

找到最合适的控件:

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

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

  • userInteractionEnabled = NO隐藏
  • hidden = YES;
  • 透明:alpha = 0.0 ~ 0.01;

具体的寻找,使用了两个方法:

- (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,则表示不在该视图内,直接返回nil
  • 若返回YES,则向当前视图的所有子视图发送hitTest:withEvent: 消息,所有子视图的遍历顺序是从最顶视图一直到最低层视图,即从 subviews 数组的末尾向前遍历,直到有子视图返回非空对象,或者全部子视图遍历完毕。
  • 若第一次有子视图返回非空对象,则hitTest:withEvent:返回此对象,处理结束。
  • 若所有子视图都返回空,则hitTest:withEvent:返回自身

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

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

其实该方法的逻辑很简单,就是先判断自己能不能响应,是不是在自己身上,不是则直接返回。是的话继续往下递归判断子事件,直到返回nil,nil之前的父控件则是最合适的控件。

pointInside:withEvent

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

解决以下问题,点击红色区域时,按钮并不会接受这个事件

在这里插入图片描述

//TabBar
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{//将触摸点坐标转换到在CircleButton上的坐标CGPoint pointTemp = [self convertPoint:point toView:_CircleButton];//若触摸点在CricleButton上则返回YESif ([_CircleButton pointInside:pointTemp withEvent:event]) {return YES;}//否则返回默认的操作return [super pointInside:point withEvent:event];
}

响应链

事件响应流程:

  • 如果找到最合适的控件来处理调用最合适的控件的touches…(touchesBegan、touchesMoved、touchedEnded)方法。
  • 如果调用了[super touch…],就会将事件顺着响应者链往上传递,传给上一个响应者,接着上一个响应者就会调用touches…方法。
  • 如果没有找到合适的控件来处理事件,则将事件传回来窗口,窗口不处理事件,将事件传给 UIApplication。如果 UIApplication 不能处理事件,则将其丢弃。

响应者

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

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

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

  • isFirstResponder:判断该View是否为第一响应者。
  • canBecomeFirstResponder:判断该View是否可以成为第一响应者。
  • becomeFirstResponder:使该View成为第一响应者。
  • resignFirstResponder:取消View的第一响应者。

这里我们需要区分一下第一响应者和最佳响应者

第一响应者和最佳响应者

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

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

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

一般来说,事件传递的目的就是为了让我们找到第一响应者。那么我们该如何判断是否为第一响应者呢?

  • 能够响应触摸事件
  • 触摸点在自己身上
  • 没有任何子视图,或是所有子视图都不在触摸点上

补充知识:

对于UIWindow,若存在多个窗口,则优先询问后显示的窗口,这和控件的逻辑是一致的,优先询问子视图和后出现的控件

什么是上一个响应者?

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

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

我们知道,响应者是从下往上传的

响应者对于事件的操作方式:

响应者对于事件的拦截以及传递都是通过 touchesBegan:withEvent: 方法控制的,该方法的默认实现是将事件沿着默认的响应链往下传递。

响应者链条是什么

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

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

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

iOS中的各种事件

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

img

与加速器,陀螺仪和磁力计相关的运动事件不遵循响应者连,Core Motion将这些事件直接传递给你指定的对象

触摸事件(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)。event绑定了touch对象

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

触摸过程:

一次完整的触摸过程,会经历一下三个状态:

  • 触摸开始:- (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

这四个触摸事件处理方法中,都有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对象,touch对象保存了触摸所属的window属性和view属性。同时event也绑定了touch对象。

UIGestureRecognizer(手势识别器)

手势识别器比UIResponder具有更高的事件响应优先级!!

UIGestureRecognizer能识别用户在某个view上面做的一些常见手势。该类是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势。

事实上,手势分为离散型手势(discrete gestures)持续型手势(continuous gesture)。系统提供的离散型手势包括点按手势(UITapGestureRecognizer)轻扫手势(UISwipeGestureRecognizer),其余均为持续型手势

UITapGestureRecognizer(敲击) UIPinchGestureRecognizer(捏合,用于缩放) UIPanGestureRecognizer(拖拽)UISwipeGestureRecognizer(轻扫) UIRotationGestureRecognizer(旋转) UILongPressGestureRecognizer(长按)

以下是UIGestureRecognizer定义

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

手势识别器会抢占UITableView的cell的点击事件。
UITapGestureRecognizer 默认 cancelsTouchesInView = YES:识别成功后会调用 touchesCancelled,中断 TableView 内部的点击流程,didSelectRowAtIndexPath可能不触发或只高亮不选择。

总结

  • 触摸发生时,系统内核生成触摸事件,先由IOKit处理封装成IOHIDEvent对象,通过IPC传递给系统进程SpringBoard,而后再传递给前台APP处理。
  • 事件传递到APP内部时被封装成开发者可见的UIEvent对象,先经过hit-testing寻找第一响应者,而后由Window对象将事件传递给hit-tested view,并开始在响应链上的传递。
  • UIRespnder、UIGestureRecognizer、UIControl,笼统地讲,事件响应优先级依次递增。

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

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

相关文章

修复图像、视频和3D场景的AI工具–Inpaint Anything

TL; DR&#xff1a;用户可以通过单击来选择图像中的任何对象。借助强大的视觉模型&#xff0c;例如SAM、LaMa和稳定扩散 (SD)&#xff0c;Inpaint Anything能够顺利地移除对象&#xff08;即Remove Anything&#xff09;。此外&#xff0c;在用户输入文本的提示下&#xff0c;I…

java -jar xxx.jar 提示xxx.jar中没有主清单属性报错解决方案

xxx.jar 中没有主清单属性 &#xff08;no main manifest attribute&#xff09;解决方案 java -jar xxx.jar 提示xxx.jar中没有主清单属性报错解决方案 这个错通常出现在你用 java -jar xxx.jar 启动&#xff0c;但 JAR 的 META-INF/MANIFEST.MF 里没有 Main-Class 条目&#…

Myqsl建立库表练习

目录 一、windows中选择一种方式安装Mysql8.0 二、新建产品库mydb6_product 1. 新建3张表如下&#xff1a; 1&#xff09;employees表 2&#xff09;orders表 3&#xff09;invoices表 三、新建员工库mydb8_worker&#xff0c;添加自定义表内容并插入数据 1. 新建库表 2. 插…

STM32 输入捕获,串口打印,定时器,中断综合运用

实验目的 使用定时器 2 通道 2 来捕获按键 2 按下时间&#xff0c;并通过串口打印。 计一个数的时间&#xff1a;1us&#xff0c;PSC71&#xff0c;ARR65535 下降沿捕获、输入通道 2 映射在 TI2 上、不分频、不滤波输入捕获原理定时器输入捕获实验配置步骤测量按键按下时长思路…

Nacos-2--Nacos1.x版本的通信原理

在Nacos 1.x版本中&#xff0c;客户端长轮询&#xff08;Long Polling&#xff09;和服务端UDP主动推送是两种不同的机制&#xff0c;分别用于配置管理和服务发现场景。它们的核心目标都是实现动态更新的实时感知&#xff0c;但实现方式、数据内容和适用场景完全不同。 1、长轮…

机器学习——09 聚类算法

1 聚类算法聚类算法&#xff1a; 是一种无监督学习算法&#xff0c;它不需要预先知道数据的类别信息&#xff0c;而是根据样本之间的相似性&#xff0c;将样本划分到不同的类别中&#xff1b;不同的相似度计算方法&#xff0c;会得到不同的聚类结果&#xff0c;常用的相似度计算…

生成式AI应用生态的爆发与专业化演进:从零和博弈到正和共赢

2025年,生成式AI产业规模已突破7000亿元,全球生成式AI市场规模预计在2028年达到2842亿美元(IDC数据)。在这场技术革命中,AI基础模型的分化已证明:差异化竞争而非同质化替代,才是推动产业发展的核心逻辑。如今,这一规律正从基础模型层向应用生成平台层蔓延——Lovable、…

Mysql——Sql的执行过程

目录 一、Sql的执行过程流程图解 二、Sql的执行过程流程 1.2.1、建立连接 1.2.2、服务层(缓存、解析器、预处理器、优化器、执行器) 1.2.2.1、缓存 1.2.2.2、解析器 1.2.2.3、预处理器 1.2.2.4、优化器 1.2.2.5、执行器 1.2.3、引擎层 一、Sql的执行过程流程图解 Sql的执行过…

【Axure高保真原型】地图路线和定位

今天和大家分享地图路线和定位的原型模版&#xff0c;载入后&#xff0c;可以查看汽车行进路线和所在定位 提供了停靠和不停靠站点两个案例&#xff0c;具体效果可以打开下方原型地址体验或者点击下方视频观看 【Axure高保真原型】地图路线和定位【原型预览含下载地址】 https…

【96页PPT】华为IPD流程管理详细版(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808811/91633108 资料解读&#xff1a;华为IPD流程管理详细版 详细资料请看本解读文章的最后内容 华为的集成产品开发&#xff08;IPD&#xff09;…

深度解析Mysql的开窗函数(易懂版)

SQL 开窗函数&#xff08;Window Function&#xff09;是一种强大的分析工具&#xff0c;它能在保留原有数据行的基础上&#xff0c;对 "窗口"&#xff08;指定范围的行集合&#xff09;进行聚合、排名或分析计算&#xff0c;解决了传统GROUP BY聚合会合并行的局限性…

Java静态代理和动态代理

Java静态代理和动态代理 静态代理 现在有一个计算类&#xff0c;有四个方法&#xff0c;加减乘除&#xff0c;如果需要给这四个方法都加上同一个逻辑&#xff0c;可以创建一个类作为代理类&#xff0c;把计算类注入到这个类中&#xff0c;然后再代理类中定义方法&#xff0c;并…

MySQL——MySQL引擎层BufferPool工作过程原理

目录一、MySQL引擎层BufferPool工作过程图解二、MySQL引擎层BufferPool工作过程原理一、MySQL引擎层BufferPool工作过程图解 图解 二、MySQL引擎层BufferPool工作过程原理 首先关闭自动提交&#xff0c;执行一条修改语句。 SET AUTOCOMMIT 0; update employees set name张三…

Python初学者笔记第二十二期 -- (JSON数据解析)

第31节课 JSON数据解析 1.JSON基础概念 JSON 是一种轻量级的数据交换格式&#xff08;另一个叫XML&#xff09;&#xff0c;具有简洁、易读的特点&#xff0c;并且在不同编程语言之间能很好地实现数据传递。在 Python 中&#xff0c;json模块能够实现 Python 数据类型与 JSON 数…

基于多模态大模型的个性化学习路径生成系统研究

摘要 随着互联网技术的迅猛发展&#xff0c;个性化学习路径生成系统的研究在教育领域日益凸显其重要性。本研究聚焦于基于多模态大模型的个性化学习路径生成系统&#xff0c;旨在通过整合多模态数据&#xff0c;为学习者提供更加精准、个性化的学习路径。多模态大模型&#xf…

ESP32 烧录固件失败原因排除

ESP32 烧录固件时&#xff0c;有哪些特殊引脚需要注意电平状态的在 ESP32 烧录固件时&#xff0c;有几个关键引脚的电平状态会直接影响烧录过程&#xff0c;需要特别注意&#xff1a;GPIO0&#xff08;BOOT 引脚&#xff09;&#xff1a;烧录模式&#xff1a;需要拉低&#xff…

3D视觉系统在机器人行业中的应用

视觉引导机器人技术&#xff08;VGR&#xff09;具有成熟的2D成像技术&#xff0c;但是经济高效的3D技术的出现使机器人应用的可能性更大。工业自动化的第一次迭代使用“盲”机器人&#xff0c;该机器人取决于待处理材料的精确定位。这样的机器人相对不灵活&#xff0c;只能通过…

MySQL高可用改造之数据库开发规范(大事务与数据一致性篇)

文章目录一、前言二、延迟的原因三、大事务处理规范3.1. 删除类操作优化设计3.2. 大事务通用拆分原则四、数据一致性核对规范4.1. 主从变更记录识别方法五、小结一、前言 MySQL 高可用架构中最基础、最为核心的内容&#xff1a;MySQL 复制&#xff08;Replication&#xff09;…

第9节 大模型分布式推理核心挑战与解决方案

文章目录 # 前言 一、通信瓶颈突破:让数据“跑”得更快 1. 问题:通信为什么会成为瓶颈? 2. 解决方案:从硬件到算法的全链路优化 (1)硬件层:升级“高速公路” (2)算法层:给数据“瘦身”并“错峰出行” (3)架构层:让数据“少跑路” 3. 效果评估:如何判断通信瓶颈已…

ESP32开发板接4阵脚屏幕教程(含介绍和针脚编号对应)

“4针屏幕” 一般有两种常见类型&#xff1a;IC 屏幕&#xff08;如 0.96" OLED、SSD1306 等&#xff09; 4 个针脚通常是&#xff1a;VCC → 接 ESP32 的 3.3V&#xff08;有的屏幕支持 5V&#xff09;GND → 接 ESP32 的 GNDSCL&#xff08;时钟&#xff09;→ 接 ESP32…