iOS事件传递与响应(二)——响应

835 阅读7分钟

       结合上文,上文讲述了关于事件的传递,通过事件的传递找到最佳的事件响应者,那么接下来该响应者就需要对该事件作出响应,去做相关的处理;连接上文,回答补充问题:

一、事件是否存在,即就是在事件发生的过程中,在什么情况下事件被丢弃了;

二、根据hitTest判断寻找最佳响应者的途径对响应事件来做排查:

  • 是否允许交互;
  • 透明度的设置;
  • 视图是否被隐藏
  • 子视图是否超出了父视图的有效范围;

也就是说如果事件能够响应的前置条件一:事件存在;二是具有最佳响应者;而最佳响应者必须通过hitTest系列判断才能找到;

在解决了事件的产生,传递以及无法响应的问题后,那么事件的响应是怎样的,接着向下看:

一、事件的响应

        事件是谁来响应的,那必然是响应者对象。上文所阐述,每一个响应者对象必定都是继承于UIResponder的对象。而为什么继承继承于UIResponder的对象就能响应事件,那是因为UIResponder中提供了对应的事件处理方法来响应事件,方法内部默认不做任何处理,只是单纯的将事件沿着响应链传递。如果要在响应链传递的过程中截获事件,对其做自定义的响应处理时,就需要对这些方法进行重写。

/// 事件开始的处理
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"事件开始");}/// 事件移动的处理- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"事件移动");}/// 事件结束的处理
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"事件结束");
}/// 事件取消的处理——该方法是在事件处理过程中被打断;/// 比如突然来电或者进程被杀死;也就是说是事件意外被中断的时候调用- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    NSLog(@"事件意外被中断时事件取消");
}

        系统通过这几种方式让事件在响应链中传递,在此过程中对某一方法进行重写,事件在响应过程中会根据重写的方法作出对应的处理。

二、事件的传递——响应链传递

        在这里需要明确一个问题是:在事件的传递中会找到最佳响应者,但是事件的响应未必就是该响应者。也就是说事件的传递链中的响应者都可以响应事件,不过最终是谁来对事件作出具体的响应还需要看响应链中的处理;

        事件在响应链中的传递依赖于:touchesBegan这个方法,每一个响应者对象都会有该方法,该方法的作用一是作为响应者的事件处理方法,二是作为在响应链中的传递方法;

这个方法默认的操作是将事件沿着默认的响应链向下传递,但是我们可以通过重写该方法对事件进行拦截处理,对此:有以下三种方式:

  • 沿着默认的响应链传递,即就是不对事件在响应链中的传递拦截;
  • 通过重写该方法拦截事件,然后让其继续沿着响应链传递(需要调用父类的该方法);
  • 通过重写该方法拦截事件,然后不让其继续沿着响应链传递(不需要调用父类的该方法);

(接上文:事件的传递:juejin.cn/post/690120…

在上文第一种情况中,也就是最终的最佳响应者是F,在该传递链中的响应链如下图二所示:

   

                                图一   视图层级                                                         图二 响应链

(一)假设:最终是由A来做事件的响应,那么响应链的传递为:

  1. F作为最佳响应者,但是F不能处理该事件,那么通过touchBegin方法将事件传给上级视图也就是他的superView  C视图;
  2. 此时C也不能处理该事件,那么同样通过touchBegin方法将事件传给上级视图也就是他的superView A视图;
  3. A此时可以处理该事件,那么最终事件就交由A来做处理;(如果A处理了该事件,则默认不会继续向上传递;但是如果当A重写了touchBegin方法且通过[super]进行了调用,那么事件继续向上传递);

(二)假设:最终是由UIApplication来做事件的响应,那么响应链的传递为:

  1. F作为最佳响应者,但是F不能处理该事件,那么通过touchBegin方法将事件传给上级视图也就是他的superView C视图;
  2. 此时C也不能处理该事件,那么同样通过touchBegin方法将事件传给上级视图也就是他的superView A视图;
  3. 此时A也不能处理该事件,那么同样通过touchBegin方法将事件传给他的控制器,UIViewControllerView;
  4. 此时UIViewControllerView也不能处理该事件,且没有向上的视图,那么通过touchBegin方法将事件传给主窗口,也就是UIWindow;
  5. 此时UIWindow也不能处理该事件,那么通过touchBegin方法将事件传给UIApplication来做处理;
  6. 此时UIApplication可以处理该事件;那么最终的事件处理就是由UIApplication来处理;

(三)假设:最终没有可以处理该事件的响应者,那么:

依然是如(二)中的步骤,不过在第6步的时候,UIApplication也不能处理该事件,那么该事件就会被丢弃;

(三)假设:最终是由多个视图响应该事件:F可以响应,且A也可以响应该事件:

  1. F作为最佳响应者且F可以处理该事件,由于要求A也能够响应该事件,所以F重写童uchBegin方法并且通过super进行调用,将事件继续向上传递到C;
  2. 此时C不能处理该事件,那么同样通过touchBegin方法将事件传给上级视图也就是他的superView A视图;
  3. A此时可以处理该事件,那么A就会对事件来做处理;且不继续向上传递;

总结:事件的响应传递链

从最佳响应者开始向上传递,直到找到可以处理该事件的视图或者视图控制器;当响应链走到UIApplication的时候,UIApplication还不能处理该事件的话,该事件就会被丢弃;当在一 个响应链中需要多个可以处理该事件的对象时,在对应的处理对象中重写touchBegin方法,super调用将事件继续向上传递;

        总结:主要是对于事件的响应做一些相关的简述,事件是如何在响应链中传递且一个响应链中想要多个对象来做响应时应该怎么做;到此,我对于事件的传递和响应进行了初步的了解,能够解决实际工作中可能遇到的一些问题。当然,这一部分的内容不止这些;

抛出以下问题,可交流:

  1. 页面卡顿的时候,用户的点击事件何时被丢弃?(涉及到RunLoop的事件循环机制,自己目前有较浅的理解);
  2. 实现事件的透传;(重写hitTest)
  3. 特殊需求的实现?底部D是底部tab的按钮,底部tab是加载在控制器根视图中的,默认情况下我们点击图中D按钮超出底部tab的部分,会发现按钮并不会得到响应。要求是实现D按钮的正常点击。如下图:

后续做深入理解后的补充。