iOS事件分发机制分析

2021-11-17  本文已影响0人  zyc_在路上

前言

iOS事件的传递与响应是一个重要的话题,此文将结合苹果官方的文档对事件的传递与响应原理及应用实践做一个比较完整的总结。文章将依次介绍下列内容:

事件的传递机制
事件的响应机制
事件传递与响应实践
手势识别器工作机制
标准控件的事件处理
iOS中事件一共有四种类型,包含触摸事件,运动事件(加速器),远程控制事件,按压事件(3D touch),本文将只讨论最常用的触摸事件。事件通过UIEvent对象描述

UIEvent

UIEvent描述了单次的用户与应用的交互行为,例如触摸屏幕会产生触摸事件,晃动手机会产生运动事件。UIEvent对象中记录了事件发生的时间,类型,对于触摸事件,还记录了一组UITouch对象,下面是UIEvent的几个属性:


1111.png

那么触摸事件中的UITouch对象描述的是什么呢?

UITouch

UITouch记录了手指在屏幕上触摸时产生的一组信息,包含触摸的时间,位置,所在的窗口或视图,触摸的状态,力度等信息

222.png

每一根手指的触摸都会产生一个UITouch对象,多个手指触摸便会有多个UITouch对象,当手指在屏幕上移动时,系统会更新UITouch的部分属性值,在触摸结束后系统会释放UITouch对象。

当事件产生后,系统会寻找可以响应该事件的对象来处理事件,如果找不到可以响应的对象,事件就会被丢弃。那么哪些对象可以响应事件呢?只有继承于UIResponder的对象才能够响应事件,UIApplication,UIView,UIViewcontroller均继承于UIResponder,因此它们均能够响应事件。UIResponder提供了响应事件的一组方法:

333.png

如果我们想要对事件进行自定义的处理(比如手指在屏幕滑动时让某个view跟着移动),我们需要重写以上四个方法,对于UIViewcontroller,我们只需要在UIViewcontroller中重写上面四个方法,对于UIView,我们需要创建继承于UIView的子类,然后在子类中重写上面的方法,这点需要注意

事件的传递

事件产生之后,会被加入到由UIApplication管理的事件队列里,接下来开始自UIApplication往下传递,首先会传递给主window,然后按照view的层级结构一层层往下传递,一直找到最合适的view(发生touch的那个view)来处理事件。查找最合适的view的过程是一个递归的过程,其中涉及到两个重要的方法 hitTest:withEvent:pointInside:withEvent:

当事件传递给某个view之后,会调用view的hitTest:withEvent:方法,该方法会递归查找view的所有子view,其中是否有最合适的view来处理事件,整个流程如下所示:

hitTest:withEvent代码实现:

444.png

事件的响应

在找到最合适的view之后,会调用view的touches方法对事件进行响应,如果没有重写view的touches方法,touches默认的做法是将事件沿着响应者链往上抛,交给下一个响应者对象。也就是说,touches方法默认不处理事件,只是将事件沿着响应者链往上传递。那么响应者链是什么呢?

响应者链

在应用程序中,视图放置都是有一定层次关系的,点击屏幕之后该由下方的哪个view来响应需要有一个判断的方式。响应者链是由一系列可以响应事件的对象(继承于UIResponder)组成的,它决定了响应者对象响应事件的先后顺序关系。下图展示了UIApplication,UIViewcontroller以及UIView之间的响应关系链:


555.png

响应者链在递归查找最合适的view的时候形成,所找到的view将成为第一响应者,会调用它的touches方法来响应事件,touches方法默认的处理是将事件往上抛给下一个响应者,而如果下一个响应者的touches方法没有重写,事件会继续沿着响应者链往上走,一直到UIApplication,如果依旧不能处理事件那么事件就被丢弃。

UIView
如果view是viewcontroller的根view,那么下一个响应者是viewcontroller,否则是super view

UIViewcontroller
如果viewcontroller的view是window的根view,那么下一个响应者是window;如果viewcontroller是另一个viewcontroller模态推出的,那么下一个响应者是另一个viewcontroller;如果viewcontroller的view被add到另一个viewcontroller的根view上,那么下一个响应者是另一个viewcontroller的根view

UIWindow
UIWindow的下一个响应者是UIApplication

UIApplication
通常UIApplication是响应者链的顶端(如果app delegate也继承了UIResponder,事件还会继续传给app delegate)

事件传递与响应实践

首先我们通过代码创建一个具有层次结构的视图集合,在viewcontroller的viewDidLoad中添加如下代码:


666.png

执行后如下所示:

要实现我们自定义的事件处理逻辑,通常有两种方式,我们可以重写hitTest:withEvent:方法指定最合适处理事件的视图,即响应链的第一响应者,也可以通过重写touches方法来决定该由响应链上的谁来响应事件。

888.png

当然,我们也可以重写绿色视图的hitTest方法,让其直接返回自身,也能实现同样效果,不过这样的话点击其它子视图(比如黄色视图)就也不能响应事件了,因此如何处理需要视情况而定。

需要说明的是,事件传递给下一个响应者时,用的是super而不是superview,这并没有问题,因为super调用了父类的实现,而父类默认的实现就是调用下一个响应者的touches方法。如果直接调用superview反而会有问题,因为下一个响应者可能是viewcontroller

手势识别器

事实上,我们要处理事件除了使用前面提到的方式,还有另一种方式,就是手势识别器。手势识别器可以很方便的处理常用的各种触摸事件,常见的手势包括单击、拖动,长按,横扫或竖扫,缩放,旋转等,另外我们还可以创建自定义的手势。

UIGestureRecognize是手势识别器的父类,所有具体的手势识别器均继承于该父类,如果我们自定义手势,也需要继承该类。然而,该类并没有继承于UIResponder,所以手势识别器并不参与响应者链。那么手势识别器是如何工作的呢?

手势识别器工作机制

当触摸屏幕产生touch事件后,UIApplication会将事件往下分发,如果视图绑定了手势识别器,那么touch事件会优先传递给绑定在视图上的手势识别器,然后手势识别器会对手势进行识别,如果识别出了手势,就会调用创建手势时所绑定的回调方法,并且会取消将touch事件继续传递给其所绑定的视图,如果手势识别器没有识别出对应的手势,那么touch事件会继续向手势识别器所绑定的视图传递。

虽然手势识别器并不是响应者链中的一员,但是手势识别器像一个观察者,会在一旁观察touch事件,并延迟事件向所绑定的视图传递,这短暂的延迟使手势识别器有机会优先去识别手势处理touch事件。

标准控件的事件处理

对于UIKit提供的的标准控件,可以很方便地通过Target-Action的方式增加事件处理逻辑(例如UIButton的addTarget方法),那么Target-Action,手势识别器,以及touches方法的优先顺序是怎样的呢?

上一篇 下一篇

猜你喜欢

热点阅读