1.UIResponser
UIResponser是iOS中用来处理用户事件的API,不仅可以接收事件还可以响应和传递事件,如果当前响应者不能处理,就转发给其它响应者处理。
2.什么是响应者
响应者顾名思义就是响应事件的对象,所有UIResponder的子类都可以作为响应者去响应事件,比如UIView、UIViewController、UIApplication等。
3.什么是第一响应者
⚠️:第一响应者并不一定就是真正响应事件的对象!
当事件到来时,系统会将事件传递给合适的响应者,并且将其成为第一响应者,第一响应者未处理的事件将会在响应链中传递,传递规则由UIResponder的nextResponder决定,可以通过重写该属性来决定传递规则。当一个事件到来时,第一响应者没有接收消息,则顺着响应者链向后传递。
4.如何查找第一响应者
查找第一响应者的方法:
返回被点击的视图
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
上面这个方法会调用下面这个方法判断点击区域是不是在视图上。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
第一响应者其实就是在点击区域并且处于最上层的视图。这个过程其实就是在获取点击屏幕的时候具体点击了那一块区域,但是点击了这一块区域并不代表这一块区域就会执行点击事件。
查找第一响应者的过程:从keyWindow开始,逐级遍历子视图,调用hitTest:withEvent:方法,判断子视图在不在点击区域,如果子视图不在点击区域或者没有子视图,那么当前视图就是第一响应者。
如果点击事件发生在视图外,但是在子视图内,那么子视图也不能成为第一响应者,因为在查找子视图的父视图的时候就已经断了。
在遍历视图时,忽略以下三种情况的视图:
视图的hidden等于YES。
视图的alpha小于等于0.01。
视图的userInteractionEnabled为NO。
查找第一响应者的过程中,就创造了一个响应者链。查找第一响应者的过程也就是创造响应者链的过程是从父类到子类一个从上到下的过程。但是这个链的方向是从下到上的,也就是从子类指向父类。
5.事件传递
事件传递的过程是一个从子类到父类,从下到上的过程。
事件到来的时候,会先传递给第一响应者,如果第一响应者不能处理就沿着响应者链向他的父视图传递,父视图会把事件传递给UIViewCotroller的view,view传递给他的控制器,控制器传递给UIWindow,UIWindow传递给UIApplication,最后传递到了UIApplicationDelegate,如果不能处理就把这个事件丢弃。
6.事件拦截
有些时候,我们可能不希望让视图的子视图去处理事件,而是想直接让该视图去处理事件。那么我们就可以重写hitTest:withEvent:方法。让当前视图成为响应者链的末端。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return self;
}
7.扩大事件响应区域
有些时候,子视图的区域可能超过了父视图的区域,这个时候点击子视图在父视图外面的那部分区域就没办法响应到,这个时候可以重写父视图的hitTest:withEvent:方法,扩大点击区域。
8.手势
⚠️:手势和点击事件是不一样的,手势是一个gesture,点击事件是一个UITouch。如果同时添加了点击事件和手势,那么手势优先执行,这是因为手势的执行优先级是高于响应者链的。
9.响应事件的逻辑
当事件到来时,会通过hitTest和pointInside两个方法,从Window开始向上面的视图查找,找到第一响应者的视图。找到第一响应者后,系统会判断其是继承自UIControl还是UIResponder,如果是继承自UIControl,则直接通过UIApplication直接向其派发消息,并且不再向响应者链派发消息。
如果是继承自UIResponder的类,则调用第一响应者的touchesBegin,并且不会立即执行touchesEnded,而是调用之后顺着响应者链向后查找。如果在查找过程中,发现响应者链中有的视图添加了手势,则进入手势的代理方法中,如果代理方法返回可以响应这个事件,则将第一响应者的事件取消,并调用其touchesCanceled方法,然后由手势来响应事件。
如果手势不能处理事件,则交给第一响应者来处理。如果第一响应者也不能响应事件,则顺着响应者链继续向后查找,直到找到能够处理事件的UIResponder对象。如果找到UIApplication还没有对象响应事件的话,则将这次事件丢弃。