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 管理的事件队列中,先进先出原则

其次: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 ->(丢弃)

posted on 2018-04-10 15:28  低头捡石頭  阅读(19)  评论(0编辑  收藏  举报

导航