响应者链

2018-08-22  本文已影响0人  为什么划船不靠桨

说响应者链之前,先说一个方法- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event,这个方法的作用是用来判断,操作的触摸点是否在当前视图上。返回YES:触摸点在当前视图上,视图会响应事件。返回NO:触摸点不在当前视图上,视图不会响应事件。重写这个方法让它返回NO,我们在点击到viewA上的时候,viewA不会响应,其父视图会响应我们操作。这样就做到了穿透viewA对viewB进行操作。还可以可以根据参数point来判断,进行点击区域的选择,可以实现,一个视图View,前半部分能响应点击,后半部分不响应点击。


然后我们再说一下UIResponder,它响应用户的操作处理各种事件。UIView,UIViewController, UIApplication都是继承于它。而UIWindow,UILabel,UIImageView是继承于UIView。所以他们都能成为响应者,成为响应者链的一环。
当一个触摸事件产生后,系统会分为两步来处理:事件的传递+事件的响应。

事件的传递

当触摸屏幕的时候,系统是这样传递这个触摸事件的:

最主要的是下面这个方法

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ 
}

其工作流程是:

  1. 视图的hidden等于YES。
  2. 视图的alpha小于等于0.01。
  3. 视图的userInteractionEnabled为NO。

总结;每个view都有这个方法,用来处理用户的操作事件。它返回:self,代表这个view会接受用户的操作事件,返回:nil,则代表这个view不会接受用户的操作事件。

事件的响应

经过以上的事件的传递过程,事件已经传递给系统认为最适合的View了。接下来就是处理这个事件。
处理事件方法:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
}

但是最适合的,不一定就能一定能处理,如果,这个View不能处理这个事件则会将这个事件上抛,就是按照事件传递下来的路线上抛。
大致流程如下:

事件拦截

有时候想让指定视图来响应事件,不再向其子视图继续传递事件,可以通过重写hitTest:withEvent:方法。在执行到方法后,直接将该视图返回,而不再继续遍历子视图,这样响应者链的终端就是当前视图。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return self;
}

事件转发

在开发过程中,经常会遇到子视图显示范围超出父视图的情况,这时候可以重写该视图的pointInside:withEvent:方法,将点击区域扩大到能够覆盖所有子视图。

还有一点需要注意,响应者链和手势同时出现时,也就是既实现了touches方法又添加了手势,会发现touches方法有时会失效,这是因为手势的执行优先级是高于响应者链的。

事件到来后先会执行hitTest和pointInside操作,通过这两个方法找到第一响应者,这个在上面已经详细讲过了。当找到第一响应者并将其返回给UIApplication后,UIApplication会向第一响应者派发事件,并且遍历整个响应者链。如果响应者链中能够处理当前事件的手势,则将事件交给手势处理,并调用touches的cancelled方法将响应者链取消。

在UIApplication向第一响应者派发事件,并且遍历响应者链查找手势时,会开始执行响应者链中的touches系列方法。会先执行touchesBegan和touchesMoved方法,如果响应者链能够继续响应事件,则执行touchesEnded方法表示事件完成,如果将事件交给手势处理则调用touchesCancelled方法将响应者链打断。

根据苹果的官方文档,手势不参与响应者链传递事件,但是也通过hitTest的方式查找响应的视图,手势和响应者链一样都需要通过hitTest方法来确定响应者链的。在UIApplication向响应者链派发消息时,只要响应者链中存在能够处理事件的手势,则手势响应事件,如果手势不在响应者链中则不能处理事件。

上一篇 下一篇

猜你喜欢

热点阅读