UI基础 - 事件 | 响应者链
■ 事件
1. 事件是由硬件捕捉并产生的一个表示用户操作设备的对象发送给 iOS!许多事件都是 UIKit框架 中 UIEvent 的实例,UIKit 当前可以识别三种类型的事件:触摸事件、摇动事件以及远程控制事件。其中触摸事件是⽤户通过触摸设备屏幕操作对象、输入数据,支持多点触摸
2. 事件传递:以触碰检测为例,硬件检测到触摸操作会将信息交给 UIApplication,然后开始检测,如下图所示
第一步 UIApplication -> window - > rootViewController
第二步 ViewA -> ViewB/ViewC(递归检测所有子视图)
第三步 ViewC -> ViewD/ViewE(最终检测到 ViewE)
注:UIApplication -> window -> viewController -> view -> 检测其所有⼦视图 ->最终确认触碰位置!如下图所示的小橘点最终由 ViewE 响应
3. 事件是如何产生的
首先:发生触摸事件后,系统会将该事件加入到由 UIApplication 管理的事件队列中,先进先出原则
再次:keyWindow 会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
最后:找到合适的视图控件后,就会调用视图控件的 touches 方法来作具体的事件处理
■ 响应者链
1. iOS 中所有能响应事件(触摸、晃动、远程事件)的对象都是响应者,系统定义了⼀个抽象的父类 UIResponder 来表示响应者,其⼦类都是响应者。顾名思义,由多个响应者对象组成的链就是响应者链
2. 我们知道了触摸事件的传递是从 UIApplication -> window -> .... -> 寻找到处理该事件最合适 View!那么到底是如何找到响应事件的那个控件 ?
首先判断自己是否能接受触摸事件:一个继承自 UIView 的视图可以重写 hitTest:withEvent方法 来限制是否将触摸事件传递给子视图
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
其次判断触摸点是否在自己身上
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
最后从后往前遍历子控件,不断重复前面的两个方法:如果没有符合条件的子控件,那么就认为自己最合适处理
注:如果 hit-test 检测出的 view 没有响应事件,事件就会沿着响应者链往下传递;如果链中某个响应者响应了事件或者已经没有响应者了,传递就会结束
3. 不参与事件传递的视图有以下几种情况
A 不允许交互的视图:userInteractionEnabled = NO
B 已隐藏的视图:如果把父控件隐藏,那么子控件也会隐藏
C 透明度:透明度为 0 或者 alpha<0.01
D 视图超出父视图的区域
4. 代码示例:响应者链
// - AppDelegate.m
1 #import "AppDelegate.h" 2 #import "ViewController.h"
1 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 2 3 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 4 self.window.backgroundColor = [UIColor whiteColor]; 5 [self.window makeKeyAndVisible]; 6 7 ViewController *firstVC =[[ViewController alloc] init]; 8 self.window.rootViewController = firstVC; 9 return YES; 10 11 }
// - ViewController.m
1 #import "FirstViewController.h" 2 #import "TouchView.h"
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 4 // 响应者链 5 TouchView *touchView = [[TouchView alloc] initWithFrame:CGRectMake(50, 100, 60, 60)]; 6 touchView.backgroundColor = [UIColor redColor]; 7 touchView.tag = 102; 8 [self.view addSubview:touchView]; 9 10 NSLog(@"[touchView nextResponder]:%@",[touchView nextResponder]); // self.view 11 NSLog(@"[[touchView nextResponder] nextResponder]:%@",[[touchView nextResponder] nextResponder]);// self 12 13 // 在 viewDidLoad 方法里,视图并未加载到 window 上,也就是说响应者链条还未建立 14 NSLog(@"[[[touchView nextResponder] nextResponder] nextResponder]:%@",[[[touchView nextResponder] nextResponder] nextResponder]);// null 15 } 16 17 // 通过响应者链可以逐级查找到响应者 18 - (void)viewDidAppear:(BOOL)animated{ 19 [super viewDidAppear:animated]; 20 21 TouchView *tapView = (TouchView *)[self.view viewWithTag:102]; 22 NSLog(@"[[[touchView nextResponder] nextResponder] nextResponder]:%@",[[[tapView nextResponder] nextResponder] nextResponder] );// window 23 NSLog(@"%@",[[[[tapView nextResponder] nextResponder] nextResponder] nextResponder]);// UIApplication 24 }
5. 响应者是如何处理事件的:以触摸事件为例
第一步:事件一旦检测到响应者,该响应者就实现相应的方法,开始处理事件
第二步:如果该响应者没有该处理事件,如交互未打开。那么它就会依次向上逐级递交任务,检验其上级是否会作处理......
第三步:若事件被递交至最高级仍未作处理,则丢弃该事件:touchView -> view -> viewController -> window -> UIApplication ->(丢弃)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律