当前位置: 首页 > news >正文

「OC」iOS事件处理流程

「OC」初识iOS事件处理流程

文章目录

  • 「OC」初识iOS事件处理流程
    • 触摸事件
    • 触摸事件的响应周期
    • 事件 响应者
      • UIEvent
      • UITouch
      • UIResponder
    • 触摸流程
      • 系统响应阶段
      • APP响应阶段
        • 寻找最佳响应者
      • 构成响应链
    • 寻找最佳响应者和响应链的区别
    • 总结
    • 参考资料

触摸事件

iOS的事件有好几种:Touch Events(触摸事件)、Motion Events(运动事件,比如重力感应和摇一摇等)、Remote Events(远程事件,比如用耳机上得按键来控制手机),其中最常用的应该就是Touch Events了,基本存在于每个app的每个地方

触摸事件的响应周期

uitouchflow.png

事件 响应者

在学习之前,还需要将基本的概念了解清楚

UIEvent

UIEvent即为事件,事件一共被分为三类,包括触摸事件(Touch Events对应就是UITouch)、运动事件(Motion Events)、远程控制事件(Remote Control Events)。

触摸的目的是生成触摸事件供响应者响应,一个触摸事件对应一个UIEvent对象,其中的 type 属性标识了事件的类型(即三种不同的时间类型)。

当我们app获取到触摸事件的时候,就会将event放置到一个事件队列之中(先触发的事件先执行,符合队列先进先出的特点)

UITouch

一个手指一次触摸屏幕,就对应生成一个UITouch对象。多个手指同时触摸,生成多个UITouch对象。

多个手指先后触摸,系统会根据触摸的位置判断是否更新同一个UITouch对象。若两个手指一前一后触摸同一个位置(即双击),那么第一次触摸时生成一个UITouch对象,第二次触摸更新这个UITouch对象(UITouch对象的 tap count 属性值从1变成2);若两个手指一前一后触摸的位置不同,将会生成两个UITouch对象,两者之间没有联系。

每个UITouch对象记录了触摸的一些信息,包括触摸时间、位置、阶段、所处的视图、窗口等信息。

手指离开屏幕一段时间后,确定该UITouch对象不会再被更新将被释放。

在UIEvent之中使用以下方法可以获得UIEvent的touch信息:

NSSet *touches = [event allTouches];
for (UITouch *touch in touches) {// 访问每个 UITouch 对象的属性CGPoint location = [touch locationInView:view];NSTimeInterval timestamp = [touch timestamp];// 其他属性...
}

UIResponder

每个响应者都是一个UIResponder对象,即所有继承于自UIResponder的对象,本身都具备响应事件的能力。因此以下类的实例都是响应者:

  • UIView
  • UIViewController
  • UIApplication
  • AppDelegate

在有关触摸的内容,我们使用以下的方法

//手指触碰屏幕,触摸开始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指在屏幕上移动
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指离开屏幕,触摸结束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸结束前,某个系统事件中断了触摸,例如电话呼入
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

触摸流程

系统响应阶段

在我们触摸屏幕的时候

  1. IOKit.framework封装整个触摸事件为IOHIDEvent对象
  2. OKit.framework通过IPC将事件转发给SpringBoard.app
  3. IOKit将触摸事件封装成一个IOHIDEvent对象,并通过mach port传递给SpringBord进程

mach port是各个进程的端口,各进程通过它来进行进程间通信
SpringBord是一个系统进程,可以理解为桌面系统。它用来统一管理系统接收到的触摸事件

APP响应阶段

前面的阶段,是通过硬件结合来进行的,接下来的阶段就是通过app来找到点击时,手指停留在view之中的位置,我们还需要将时事件传递给具体被点击的View上。 我们要处理这个问题,就是需要使用响应链。

img

寻找最佳响应者

在UIView之中存在以下两个方法

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;  //返回触发点击的view
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;  //判断坐标在哪个view的管辖范围内

通过这两个方法,通过上面图片给出的流程,不断循环追溯到准确的响应子视图 ,第二个方法是包含在第一个方法之中的。

这一过程主要来确定由哪个视图来首先处理 UITouch 事件。当你点击一个 view,事件传到 UIWindow 这一步之后,会去遍历 view 层级,直至找到那个合适的 view 来处理这个事件,这一过程也叫做 Hit-Testing。而在传递至UIWindow之前,UIApplication先将事件通过 sendEvent: 传递给事件所属的window,window同样通过 sendEvent: 再将事件传递至view之中。

系统会根据添加 view 的前后顺序,确定 view 在 subviews 数组中的顺序。然后根据这个顺序将视图层级转化为图层树,针对这个树,使用倒着进行前序深度遍历的算法,进行遍历。

前序深度遍历的具体流程如下:

  • 如果点不在这个视图内,则去遍历其他视图。
  • 如果点击在这个视图内,但是其还有子视图,那么将事件传递给子视图,并且调用子视图的 [hitTest:withEvent:].
  • 如果点击在这个视图内,并且这个视图没有子视图,那么 return self,即它就是那个最合适的视图。
  • 如果点击在这个视图内,并且这个视图没有子视图,但是不想作为处理事件的 view,可以 return nil,事件由父视图处理。

注:以下三种情况UIView以及其子View的hitTest(_:with:)不会被调用,而且子UIView不接收任何触摸事件

userInteractionEnabled = NO
hidden = YES
alpha = 0.0~0.01之间(透明度<0.01即为透明)

UIImageViewuserInteractionEnabled默认为NO,因此UIImageView以及它的子控件默认是不接收触摸事件的。

通过以上内容,我们可以仿照一个相关的方法hitTest:withEvent:,以下直接照抄大佬给出的内容:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{//3种状态无法响应事件if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil; //触摸点若不在当前视图上则无法响应事件if ([self pointInside:point withEvent:event] == NO) return nil; //从后往前遍历子视图数组 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) {//如果子视图中有更合适的就返回return fitView; }} //没有在子视图中找到更合适的响应视图,那么自身就是最合适的return self;
}

构成响应链

最佳响应者的最佳,其实就是这个UIResponder对象具有响应对应事件的最高权限,每一个响应者对象(UIResponder对象)都有一个 nextResponder 方法,用于获取响应链中当前对象的下一个响应者。

  • 若视图是控制器的根视图,则其nextResponder为控制器对象;否则,其nextResponder为父视图。
  • UIViewController
    若控制器的视图是window的根视图,则其nextResponder为UIWindow对象;若控制器是从别的控制器present出来的,则其nextResponder为presenting view controller。
  • UIWindow
    nextResponder为UIApplication对象。
  • UIApplication
    若当前应用的app delegate是一个UIResponder对象,且不是UIView、UIViewController或app本身,则UIApplication的nextResponder为app delegate。

通过以上的方式我们就可以通过寻找nextResponder将响应链完整的构建出来。

响应者对于事件的拦截以及传递都是通过 touchesBegan:withEvent: 方法控制的,该方法的默认实现是将事件沿着默认的响应链往下传递。响应者链存在的意义为提供一种机制,让未被直接交互的对象也有机会处理事件,增加了事件处理的灵活性。

img

寻找最佳响应者和响应链的区别

  • 寻找最佳响应者:
    • 从最底层的视图开始,自下而上地检查视图层级。
    • 使用hitTest:withEvent:方法来确定哪个视图包含了触摸点。
    • 考虑视图的属性,如是否隐藏、是否启用用户交互等。
  • 响应者链:
    • 从最佳响应者开始,沿着预定义的路径向上传递。
    • 通常的路径是:UIView → UIViewController → UIWindow → UIApplication → UIApplicationDelegate。

总结

以上就是对触摸事件以及响应者链的学习内容,接下来还有UIResponder、UIGestureRecognizer、UIControl这个几个触发响应的优先级,以及响应事件内部的深入探究,由于篇幅我们便将剩下的内容留到下一篇博客吧。

参考资料

01 触摸事件传递

iOS事件处理

iOS——事件、响应链和传递链

iOS触摸事件全家桶

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Vu3 跨组件通讯
  • ASPICE评估前的重要准备事项
  • 【 html+css 绚丽Loading 】000037 六合归一心
  • Oracle 和 PostgreSQL 常用数据类型的对比
  • 2024前端面试题分享
  • 【Grafana】Prometheus结合Grafana打造智能监控可视化平台
  • Python requests库详细介绍
  • 设计模式 —— 单例模式
  • OpenCV图像与视频分析笔记 — 视频部分
  • Mendix 创客访谈录|Mendix赋能汽车零部件行业:重塑架构,加速实践与数字化转型
  • 数字电子技术-进制
  • WPS快捷键
  • HarmonyOS开发实战( Beta5版)状态管理优秀实践
  • Spark MLlib模型训练—回归算法 GLR( Generalized Linear Regression)
  • IntelliJ IDEA修改默认.m2和.gradle缓存路径
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  •  D - 粉碎叛乱F - 其他起义
  • Java Agent 学习笔记
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • Java面向对象及其三大特征
  • Median of Two Sorted Arrays
  • nodejs:开发并发布一个nodejs包
  • nodejs调试方法
  • Vim Clutch | 面向脚踏板编程……
  • Vue UI框架库开发介绍
  • 服务器从安装到部署全过程(二)
  • 关于for循环的简单归纳
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 我的zsh配置, 2019最新方案
  • 一个SAP顾问在美国的这些年
  • 赢得Docker挑战最佳实践
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • 智能合约Solidity教程-事件和日志(一)
  • 积累各种好的链接
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • #includecmath
  • #NOIP 2014# day.1 T2 联合权值
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • $.ajax()参数及用法
  • (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (层次遍历)104. 二叉树的最大深度
  • (二)WCF的Binding模型
  • (附源码)ssm码农论坛 毕业设计 231126
  • (附源码)计算机毕业设计高校学生选课系统
  • (每日一问)操作系统:常见的 Linux 指令详解
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (新)网络工程师考点串讲与真题详解
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转)项目管理杂谈-我所期望的新人