前言
作为UI部分的第一篇文章,我决定写这篇关于事件分发,和响应者链条传递的过程的博文,因为这块知识是UI部分最基础的,但也是最容易让我们所忽视的,直到,当我们写页面时,发现我们认为该响应点击的控件并没有如我们想象,我们才发现自己对这一块知识我们其实并没有吃透,想当然的以为会按照看起来应该的方式响应。
简述区别
事件分发和响应者链条,完完全全是两件事,两个过程,同学们不能把它混为一谈,虽然,先进行事件分发,然后再通过响应者链条查找到响应者,这两个过程加起来完成了一次对事件的处理,但它们绝不是一回事。
简单来说,这两个过程所做的事情:
事件分发:自上而下的由UIApplication开始,一路往最具体的View查找,直到找到最应该处理并且能够处理事件的那个控件。
响应者链条:当找到那个最应该处理并且能够处理事件的那个控件以后,如果这个控件确实处理了这个事件,那么这个事件的就到此处理完毕,但是很有可能出现的情况是,虽然这个控件最应该处理,也能够处理事件,但是它并没有处理事件,那么这时这个事件就要传给下一个响应者处理,下一个响应者还不处理,那就再下一个,这个事件就沿着这条响应者链条找直到响应者确实处理了这个事件(并中断了事件传递,就是在touch方法里没有调用下一个响应者的touch方法)为止
很重要的一点:事件的分发路径和响应者链条的路径并不是同一条路从两头走的关系。
首先,事件分发时候除了最开始的UIApplication,一路都是在查找下一个更应该响应的UIView,而响应者链条除了考虑UIView以外还有其他的UIResponder,例如UIViewController;其次,事件分发会考虑同级UIView之间的关系,就是如果一个UIView有多个子View,那么哪个是更应该响应的View,而响应者链条则不会考虑同级View之间的关系,一个View的下一个响应者并不会考虑除了自己以为其它适合响应的同级View,而是之间考虑它的父View或者是控制器。
这就决定了这两个过程走的绝对不是同一条路(即使看起来有点像)。
事件分发
当我们手指触摸屏幕时,系统会生成UITouch对象(一个手指一个UITouch),然后系统又会帮我们把所有的UITouch对象包装成一个UIEvent,然后把这个UIEvent交给UIApplication单例维护着的一个事件队列,当轮到这个事件处理的时候,UIApplication首先会将这个UIEvent交给KeyWindow,然后KeyWindow再交给它的根控制器的View,然后不断的递归寻找最适合处理这个事件的View。
这个递归查找的方法叫做hitTest
下面我们尝试的写一下这个方法的实现:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//如果不能处理,返回nil
if (self.hidden==YES||self.alpha<=0.01||self.userInteractionEnabled == NO) {
return nil;
}
//如果不应该处理返回nil
if ([self pointInside:point withEvent:event]==NO) {
return nil;
}
//找找看有没有更适合处理的子View
//最后添加的(也就是最上面的View)先处理
NSArray *reverseArray = [[self.subviews reverseObjectEnumerator]allObjects];
for (UIView *subView in reverseArray) {
CGPoint subPoint = [self convertPoint:point toView:subView];
UIView *resultView = [subView hitTest:subPoint withEvent:event];
if (resultView) {
return resultView;
}
}
//遍历完都没有子控件更适合,那么最适合的就是自己
return self;
}
响应者链条
当找到这个最适合的控件后,会从这个控件开始尝试处理事件并传递给下一个响应者(直到某个响应者中断了传递)。
那么,怎么寻找下一个响应者呢?原则如下:
如果这个view是一个控制器的View那么这个View的下一个响应者就是控制器。
如果这个View不是控制器的View那么下一个响应者就是它的父View。
根控制器的下一个响应者是谁UIWindow,再下一个响应者是UIApplication,链条结束。
如果我们把A控制器的View添加到B控制器的View上,那么A控制器会在响应者链条上吗,如果在,它的下一个响应者是谁?
如果要把A控制器添加到响应者链条,就要B控制器add A为子控制器,如果不add的话A控制器不会在这个响应者链条内,A控制器的下一个响应者为A控制器View的父View。