iOS事件响应
2019-08-10 本文已影响1人
鲲鹏DP
24CF98B1-83C5-4206-99B2-B9FFE552AF98.png
1.系统响应阶段
- a).手指触碰屏幕,屏幕感应到触碰后,将事件交由IOKit处理。
- b).IOKit将触摸事件封装成一个IOHIDEvent对象,并通过mach port传递给SpringBoad进程。
- c).SpringBoard进程因接收到触摸事件,触发了主线程runloop的source1事件源的回调。此时SpringBoard会根据当前桌面的状态,判断应该由谁处理此次触摸事件
2.寻找最佳响应者
- 1.从根视图往上寻找,每个视图干3件事;
a).当前视图能否响应事件(hitTest:withEvent:),self.userInteractionEnabled == YES && self.hidden == NO && self.alpha > 0.01
b).点击的坐标点是否在当前视图内(pointInside:withEvent:)
c).遍历子视图,分别判断上面两个条件,有子视图满足条件则最佳在其子视图中,没有则发回nil,当前视图为最佳响应视图; - 2.寻找最佳响应视图完成后,事件响应者链也就生成了,事件响应者链由一些列继承至UIResponder的元素组成。
3.事件分发
找到最佳响应视图后,会调用最佳响应视图的touch相关方法,如果没有实现该方法,就会继承关系,逐一往上查找,如果某个祖先视图实现了,事件就交给它处理,一直没有找到,事件到了UIAapplication就会被抛弃;
代码分析
-
创建三个视图
9C1B6B33-50FE-41EC-8C59-0A60D98A26D0.png -
三个视图都继承自一个BaseView,在BaseView中交换hitTest:withEvent:,pointInside:withEvent:和touchesBegan:withEvent:三个方法的实现,输出提示信息
#import "BaseView.h"
#import <objc/message.h>
@implementation BaseView
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
Method originMethod1 = class_getInstanceMethod(class, @selector(hitTest:withEvent:));
Method newMethod1 = class_getInstanceMethod(class, @selector(DKP_hitTest:withEvent:));
BOOL didAddMethod1 = class_addMethod(class, @selector(hitTest:withEvent:), method_getImplementation(newMethod1), method_getTypeEncoding(newMethod1));
if (didAddMethod1) {
class_replaceMethod(class, @selector(DKP_hitTest:withEvent:),method_getImplementation(originMethod1), method_getTypeEncoding(originMethod1));
}else{
method_exchangeImplementations(originMethod1, newMethod1);
}
Method originMethod2 = class_getInstanceMethod(class, @selector(pointInside:withEvent:));
Method newMethod2 = class_getInstanceMethod(class, @selector(DKP_pointInside:withEvent:));
BOOL didAddMethod2 = class_addMethod(class, @selector(pointInside:withEvent:), method_getImplementation(newMethod2), method_getTypeEncoding(newMethod2));
if (didAddMethod2) {
class_replaceMethod(class, @selector(DKP_pointInside:withEvent:),method_getImplementation(originMethod2), method_getTypeEncoding(originMethod2));
}else{
method_exchangeImplementations(originMethod2, newMethod2);
}
Method originMethod3 = class_getInstanceMethod(class, @selector(touchesBegan:withEvent:));
Method newMethod3 = class_getInstanceMethod(class, @selector(DKP_touchesBegan:withEvent:));
BOOL didAddMethod3 = class_addMethod(class, @selector(touchesBegan:withEvent:), method_getImplementation(newMethod3), method_getTypeEncoding(newMethod3));
if (didAddMethod3) {
class_replaceMethod(class, @selector(DKP_touchesBegan:withEvent:),method_getImplementation(originMethod3), method_getTypeEncoding(originMethod3));
}else{
method_exchangeImplementations(originMethod3, newMethod3);
}
});
}
-(UIView *)DKP_hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%@------%s",NSStringFromClass([self class]),__func__);
return [self DKP_hitTest:point withEvent:event];
}
-(BOOL)DKP_pointInside:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%@------%s",NSStringFromClass([self class]),__func__);
return [self DKP_pointInside:point withEvent:event];
}
-(void)DKP_touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%@------%s",NSStringFromClass([self class]),__func__);
}
- 点击最顶上黄色视图
D3A6192A-BAB4-45BD-9B8F-77A68A3F18A6.png
结果:找到了thirdView为最佳响应者,开始处理事件,调用thirdView的touche方法。由于其实现了改发放,事件就交由thirdvie处理了。 - 点击蓝色视图:预计结果应该是secondView响应touche方法
- 点击红色视图
773B202C-340C-4358-9E71-D74055607E08.png
结果:黄色的thirdView没有调用hitTest和pointInside方法。因为,点击点在oneView中,不在secondView和thirdView中。寻找最佳响应者时,当遍历oneView的子视图,递归判断时,发现secondView不包含点击点,直接返回了nil,所以就不会再遍历secondView的子视图了,自然不会调用thirdView的相关方法。
应用
扩大视图响应事件的范围,或则让指定视图响应事件
将上面的hitTest方法稍作修改,寻找最佳视图到了secondView时,return self,指定secodView为最佳响应者。
-(UIView *)DKP_hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%@------%s",NSStringFromClass([self class]),__func__);
if ([NSStringFromClass([self class]) isEqualToString:@"secondView"]) {
return self;
}
return [self DKP_hitTest:point withEvent:event];
}
再来点击黄色的thirdView
6B2E45CB-F6EF-4427-B957-0B049A9DF315.png结果:事件响应者链在secondView就中断了,scondView响应事件。
最后
- 寻找最佳响应者和事件传递顺序是相反的。前者从父到子,后者从子到父;
- 寻找到的最佳响应者,并非就一定是处理事件的控件,最佳响应者只是处在事件处理的第一顺位,最先判断他是否有处理本次事件的能力而已。
- 可以在hitTest方法中,改变最佳响应者,解决开发中一些事件响应相关的需求。