iOS开发这么多年,其实历来就没关心过期间传递和响应机制这么个事。当我看到这篇文章史上最详细的iOS之事件的传递和响应机制-原理篇后,发现其中有不少东西能够细细品味一下的。ios
整个事件传递和处理流程,简单归纳为:api
事件-事件传递到指定界面-找到可响应的界面-响应数组
我开始的理解误区就是‘传递到指定界面’和‘可响应界面’理解成同一个界面了,形成我在看上面的文章的时候,有些混乱。其实这两个能够是两个界面。markdown
例如:我在touchBegin一个view的时候,需求是view不响应,而superview响应。而事件传递是传递到view中。这种状况两个view就是不相同的界面。app
若是不想让view处理事件,而是想让superview处理,就能够吧view的userInteractionEnabled设置为no。ide
系统api中提供了两个方法,函数
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds
复制代码
为了方便:hitTest:withEvent:方法在文章后续用hitTest代替,pointInside:withEvent:用pointInside代替oop
经过注释了解到hitTest方法是递归的调用pointInside方法。point是在接受控件坐标系内的。this
底层的事件传递实现就是: 产生触摸事件->UIApplication事件队列->[UIWindow hitTest:withEvent:]->返回更合适的view->[子控件 hitTest:withEvent:]->返回最合适的view->...->返回最合适的viewspa
咱们能够重写hitTest方法,来拦截系统的时间传递,让指定的view处理事件。例如自定义view中,想让view中的一个subview处理事件,就能够在自定义view中重写该方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
return self;
}
return nil;
}
复制代码
示例代码我返回的是self,这里能够改为指定的subview,或者遍历subview中的一个。
在不少文章中都看到了这张图,不清楚是否是官方,但图片中的逻辑是没有问题的,ios控件间的摆放都是有层级关系的,这张图表示的很清晰。响应者对象就是继承与UIResponder的子类们。
UIResponder的子类有一下几个:
p.s. UIWindow的父类是UIView
UIResponder的子类是经过nextResponder进行链接的。
响应链建立方式,本人我的理解,应该是链表的头插法形式:
这里只是简单举个例子,其实项目中会有更复杂的层级关系。
不少人会问如何证实呢,咱们来看看官方文档中的解释:
Returns the next responder in the responder chain, or nil if there is no next responder.
返回响应者链中的下一个响应者,如没有下一个响应者返回nil。
The UIResponder class does not store or set the next responder automatically, so this method returns nil by default. Subclasses must override this method and return an appropriate next responder. For example, UIView implements this method and returns the UIViewController object that manages it (if it has one) or its superview (if it doesn’t). UIViewController similarly implements the method and returns its view’s superview. UIWindow returns the application object. The shared UIApplication object normally returns nil, but it returns its app delegate if that object is a subclass of UIResponder and has not already been called to handle the event.
UIResponder类不会自动存储和设置下一个响应者(next responder),这个方法默认返回nil。子类必须复写这个方法而且返回一个合适的下一个响应者。例如,UIView实现这个方法,若是是被UIViewController对象管理的下一个响应者就是UIViewController;如不不是被UIViewController对象管理的,下一个响应者就是superview。UIViewController一样实现这个方法,而且返回它本身view的superview。UIWindow返回application对象。shared UIApplication对象一般返回nil,可是若是该对象是一个UIRespnder的子类而且尚未被调用去处理事件,它返回的是app的delegate。
经过上面例子中的响应链自定义view->superview->rootViewController->keyWindow->UIApplication->AppDelegate->nil的顺序,逐层向后查找可作响应的响应者(UIResponder子类)。
若是多层有实现了UIResponder的相关方法,例如touchesBegan,这多层均可以响应。
举个例子: vc中init一个自定义的TestView,而且在vc和TestView中都实现了touchesBegan方法
vc部分代码:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.lightGrayColor;
TestView *view1 = [[TestView alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
view1.tag = 1;
view1.backgroundColor = [UIColor redColor];
[self.view addSubview:view1];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
[super touchesBegan:touches withEvent:event];
}
复制代码
TestView部分代码:
@implementation TestView
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
[super touchesBegan:touches withEvent:event];
}
复制代码
运行后的效果: 点击红色区域后查看控制台:
TestView和VC的touchesBegan方法都调用了。
注意:TestView中的touchesBegan要调用super touchesBegan,若是不调用,vc中没法打印。由于不调用就不会继续查找响应链中后续的响应者了。vc中touchesBegan中调用了super也是同理目的。
事件的传递和响应的区别: 事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件)。
参考这篇文章:iOS事件响应链中hitTest的应用示例
其中包括: