# iOS开发:剖析UITouch传递链和响应链
Posted wuwuFQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了# iOS开发:剖析UITouch传递链和响应链相关的知识,希望对你有一定的参考价值。
每一次温柔或强烈的触摸,都会上演一场你争我抢的小电影!
故事背景:
UITouch:(起因)
- 一个手指一次触摸屏幕,就对应生成一个UITouch对象。多个手指同时触摸,生成多个UITouch对象。
- 多个手指先后触摸,系统会根据触摸的位置判断是否更新同一个UITouch对象。若两个手指一前一后触摸同一个位置(即双击),那么第一次触摸时生成一个UITouch对象,第二次触摸更新这个UITouch对象(UITouch对象的 tap count 属性值从1变成2);若两个手指一前一后触摸的位置不同,将会生成两个UITouch对象,两者之间没有联系。
- 每个UITouch对象还包含指示触摸发生时间的时间戳、表示用户点击屏幕次数的整数以及描述触摸是开始、移动还是结束的常量形式的触摸阶段,或者系统是否取消了触摸。
//触摸的各个阶段状态
//例如当手指移动时,会更新phase属性到UITouchPhaseMoved;手指离屏后,更新到UITouchPhaseEnded
typedef NS_ENUM(NSInteger, UITouchPhase)
UITouchPhaseBegan, // whenever a finger touches the surface.
UITouchPhaseMoved, // whenever a finger moves on the surface.
UITouchPhaseStationary, // whenever a finger is touching the surface but hasn't moved since the previous event.
UITouchPhaseEnded, // whenever a finger leaves the surface.
UITouchPhaseCancelled, // whenever a touch doesn't end but we need to stop tracking (e.g. putting device to face)
;
UIEvent:(要做的事)
-
触摸的目的是生成触摸事件供响应者响应,一个触摸事件对应一个UIEvent对象,其中的 type 属性标识了事件的类型(触摸事件,加速计事件、远程控制事件)。
-
触摸事件对象包含与事件有某种关系的触摸(即屏幕上的手指)。一个触摸事件对象可能包含一个或多个触摸,每个触摸由一个UITouch对象表示。当触摸事件发生时,系统会将其路由到适当的响应者并调用适当的方法,例如
touchesBegan(_:with:)
。 -
应用程序可以接收许多不同类型的事件,包括触摸事件、运动事件、遥控事件和按压事件。触摸事件是最常见的,并被传递到最初发生触摸的视图。运动事件是
UIKit
触发的,与Core Motion
框架报告的运动事件是分开的。远程控制事件允许响应者对象接收来自外部附件或耳机的命令,以便它可以管理音频和视频——例如,播放视频或跳到下一个音轨。按下事件表示与游戏控制器、AppleTV 遥控器或其他具有物理按钮的设备的交互。您可以使用type
和subtype
属性确定事件的类型。
UIResponder:(谁去做这件事)
每个响应者都是一个UIResponder对象,即所有派生自UIResponder的对象,本身都具备响应事件的能力。因此以下类的实例都是响应者:
- UIView
- UIViewController
- UIWindow
- UIApplication
响应者之所以能响应事件,因为其提供了4个处理触摸事件的方法:
//手指触碰屏幕,触摸开始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指在屏幕上移动
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//手指离开屏幕,触摸结束
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
//触摸结束前,某个系统事件中断了触摸,例如电话呼入
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
那么,咱们现在知道了,有一个手指触摸了屏幕生成了触摸对象UITouch,并产生了触摸事件UIEvent,那么让哪个视图(UIResponder)去处理这次事件呢?后宫嫔妃众多,这次不可能雨露均沾,只能翻牌决定了!
因此这就存在一个寻找事件最佳响应者(又称第一响应者 First Responder)的过程,目的是找到一个具备最高优先级响应权的响应对象(the most appropriate responder object),这个过程叫做Hit-Testing(命中测试),那个命中的最佳响应者称为hit-tested view。
传递链
寻找第一响应者所形成的链表我们称为事件传递链
。
传递链用到的主要函数:hitTest:withEvent:
、 pointInside:withEvent:
寻找命中测试视图或者叫第一响应者的条件是:
- alpha > 0.01
- userInteractionEnable = YES
- hidden = NO
- 触摸点在视图坐标内(视图不可超出父控件区域)
- 点击屏幕产生触摸事件,系统将这个事件加入到一个由UIApplication管理的事件队列中,UIApplication会从消息队列里取事件分发下去,首先传给UIWindow
- 在UIWindow中就会调用hitTest:withEvent:方法去返回一个最终响应的视图
- 在hitTest:withEvent:方法中就会去调用pointInside: withEvent:去判断当前点击的point是否在UIWindow范围内,如果是的话,就会去遍历它的子视图来查找最终响应的子视图
- 遍历的方式是使用倒序的方式来遍历子视图,也就是说最后添加的子视图会最先遍历,在每一个视图中都回去调用它的hitTest:withEvent:方法,可以理解为是一个递归调用
- 最终会返回一个响应视图,如果返回视图有值,那么这个视图就作为最终响应视图,结束整个事件传递;如果没有值,那么就会将UIWindow作为响应者
响应链
- 我们已经知道寻找第一响应者所形成的是传递链,那么按照传递链的反向路径由各个响应者所组成链表叫做
响应链
。 - 传递链的目的是找到第一响应者,而这个过程形成了一条有效的响应者路径;响应链的目的是找到最终响应者。
再通俗一点:
- 手指触摸屏幕上的一点,谁来处理这个事件,那么我们就要找点击在了哪些视图的有效范围内,从
UIwindow
窗口开始找,一直找到最靠近用户的视图(也就是最外层),自下而上找到了可以响应这个点击的所有视图,这些响应者视图组成了传递链; - 传递链上的视图都是响应者,响应者可以响应事件,但可能没有添加点击事件或者没有实现
touchesBegan:
等方法,就无法处理事件,接下来就是寻找谁能处理这次的事件,按照响应链来依次响应,都没有响应处理,那么这次点击就当做一次误触处理,不作任何反应。
个人理解总结
上图的视图结构是:橘色 --> 灰色(黄色) --> 红色(绿色) --> 蓝色。()内是同级视图,是后添加的。
我们给灰色视图添加一个点击事件,然后手指点击蓝色视图。打印如下:
上面的日志是在系统 (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
里面打印的,看打印顺序,你可能就明白我之前讲的一大堆了,打印的日志就是寻找第一响应者的过程:从底部开始,倒序循环子视图,最后找到了蓝色是第一响应者,再反方向,按照响应链,找到了灰色视图。
通透了吧! Nice!
The End
以上是关于# iOS开发:剖析UITouch传递链和响应链的主要内容,如果未能解决你的问题,请参考以下文章