ios触摸超出_深入 iOS 触摸事件捕获

触摸事件的处理是移动开发中十分重要的一个环节,之前研究过 Android 的触摸事件处理机制,它与 iOS 的处理机制十分不同,可以简单描述一下:

Android 在驱动层发出触摸事件后经过处理通过 IPC 通知给应用,然后应用在 Framework 层面(Java 层)由 Activity 派发封装好的事件给最上层的 View(称之为 DecorView),一旦这个事件派发到了 View 之后,我们就可以完全掌控这个事件的流向和处理方法了。最主要的两个方法就是 onTouchEvent 和 onInterceptTouchEvent,事件首先会沿着视图层次,递归调用 onInterceptTouchEvent 方法,如果这个方法返回 true,那么表示这一层次的 View 已经接管了之后所有的触摸事件(包括移动和抬起);如果返回 false,则继续向下递归,当位于最底层(相对屏幕最靠近用户的一层)时,开始调用 onTouchEvent,然后沿着原路向上冒泡,哪一层的 onTouchEvent 返回了 true,则之后所有的触摸事件交给这层 View。时序图大体是这个样子(用 TE 表示 onTouchEvent,用 ITE 表示 onInterceptTouchEvent):

ITE (A) -> ITE (B) -> TE (C) -> TE (B) -> TE (A)

视图的层次关系是 A { B { C } },显然事件从 A 开始向下传递,但是响应是从 C 冒泡向上,但全程开发者可控。Android 的事件处理机制可以让开发者轻松实现一个重要的机制:捕获机制。

什么是捕获机制?

举个例子,当一个 Scroll View 里放置了一个 UIControl (如 UISlider) 的时候,UISlider 的滑动会阻止 Scroll View 的滚动,这时 UISlider 就捕获了 UIScrollView 的触摸事件了。

iOS 如何处理捕获?

iOS 捕获触摸事件的原理与 Android 完全不同。通过查看 UISlider 处理 touchesMoved 时的函数调用栈能够看出:

栈顶第 4 个函数(我这里用了 Swift 所以函数名经过 Naming Convention 了)是 MySlider(自己写的 UISlider 子类) 的 touchesMoved 函数,但是调用它的函数却是 UIWindow 下的一个私有方法,而并不是通过 traversal 所有视图的方式,逐级将触摸事件传递到子视图,这很有趣。

为什么会这样?这还要追溯到 iOS 事件传递的方式上。大家对 UIResponder 和 Responder Chain 应该都很熟悉,苹果在文档中介绍过,任何事件都会由 Cocoa Framework 封装后传递给 Initial View(对于触摸事件而言)或 First Responder(对于 Remote Control、Action 或实体按键事件),如果响应方法调用了 super 方法,Framework 会接管事件的 forward 过程从而将事件沿着 Responder Chain 传递。

下面这个图片应该更清晰一些:

然后我们讨论捕获的问题。

其实对于普通 UIView 子类而言,并不存在什么捕获的问题,框架也不给你机会,因为当 UIResponder 的处理方法被调用时,那仅仅像是系统通知了你一下:这有个事件,处不处理你看着办吧。如果 Scroll View 里放了一个 UIView 的子类,你根本没有办法通过 UIResponder 的方法捕获触摸事件,当你滑动手指时,一个 cancel 事件一定会传递到你的 UIView 子类中的。唯一可以做到捕获事件作用的就是 iOS 3 引入的 UIGestureRecognizer。系统对它的处理方法也比较特殊,甚至会绕过 UIWindow 和 UIApplication 的 sendEvent 方法,直接由 RunLoop 中的一个 0 号 Source 派发给 Gesture 处理系统。虽然我们看不到 UIKit 的源码,但目前为止可以认为是定理的就是 Gesture 的处理优先级 > Responder 标准事件处理的优先级。

下面我们看看为什么 UIControl 不会被 Scroll View 夺去触摸控制权。

我们看 UIScrollView 的这两个方法(属性):

其中很重要的是 canCancelContentTouches 属性:

如果这个属性设为 YES,当 Scroll View 的 subviews 有一个已经开始处理触摸事件了,在 Scroll View 开始滑动时,正在处理触摸事件的 subview 就会收到 cancel 事件,然后触摸处理权就被 Scroll View 截获了。

UIScrollView 处理触摸的原理很简单,就是一个 UIPanGestureRecognizer 的子类,而其 Delegate 有一个gestureRecognizerShouldBegin: 方法,个人猜测 UIScrollView 通过这个代理方法来确定是否要启动 Gesture 处理来截获触摸事件。

最后,至于为什么 UIControl 不会被截获,那是因为:

并不是因为 UIControl 很厉害...如果不想被 UIButton 拦截滚动,那就可以复写这个方法来解决了。

先写这些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值