ios开发

触摸、事件和响应者那些事

2017-11-05  本文已影响16人  我们是斗士

这篇文章是我在阅读相关苹果官方文档后总结整理出来的一些平常可能不太注意到,但是又比较有用的知识点。如有错误,欢迎指出。

事件传递

事件本质

什么是事件?官方文档的解释是:

Events in iOS represent fingers touching views of an application or the user shaking the device. One or more fingers touch down on one or more views, perhaps move around, and then lift from the view or views. As this is happening, iPhone’s Multi-Touch system registers these touches as events and sends them to the currently active application for processing

当触摸事件发生时,系统会把触摸注册为一个事件(Event),传递给系统处理。一个完整的手势过程,是从第一根手指触碰到屏幕开始,到最后一根手指离开屏幕为止。

当然,手机的摇晃也要算是Event(它属于UIEventType里的motion),不过那是另一回事了,我们后边会再说。

屏幕上的每一个触摸点用UITouch来表示。在整个手势过程中,每一个UITouch对象会被系统持有,但是它的状态是可变的,分别要经历touchesBegan, touchesMovedtouchesEnded 三个状态。
当然,个别的时候,一个UITouch会经历第四个状态:touchesCanceled。一个事件被取消通常是由于一个外部事件(例如来电)的产生,让系统终止了本次touch事件。

UITouch

每一个UITouch对象表示一根手指对屏幕的触摸,包含位置、大小、移动状况以及触摸的力度(力度仅在支持3Dtouch或者Apple Pencil的设备上管用)。
UITouch类有以下我觉得比较重要的属性和方法:

响应者链

UIResponder

UIResponder是一个抽象类,被苹果称为事件处理的“主心骨”。具体到事件发生时,继承自UIResponder的对象主要有两个方面的职责:

  1. 通过覆写四个关于touches的方法,拦截并处理事件(如果该对象需要响应事件的话)。
  2. 将事件顺着响应者链向上传递(如果该对象不需要响应该事件的话)。

另外,inputView也可以作为事件的响应(在这里我把它理解为“输入响应”)。例如,当我们点击一个textView,这个view会变成First Responder,并显示它的 inputView。关于inputView和firstResponder我们会另起一篇文章来详细描述它。

接下来我们看几个UIResponder当中重要的属性和方法:

顾名思义,它表示响应者链中的下一个响应者。值得注意的是,UIResponder本身并不存储或者预先设置任何值给nextResponder,该属性默认设置为nil。到底谁是nextResponder还需要继承自它的类自己来覆写。例如,一个View的nextResponder可能是它的superView(如果有的话),也可能是viewController(如果该view就是根视图)。一个ViewController的nextResponder可能是UIWindow(如果其根视图是这个window的root view的话),也可能是另一个viewControllerB(如果viewController嵌套在viewControllerB中显示的话)。UIWindow的nextResponder就是UIApplication。UIAPPlication的nextResponder就是appDelegate(当且仅当这个delegate是UIResponder的实例而非一个view,viewController,或者app object本身)。

  1. 该方法的默认实现是将事件沿响应者链向上传递。因此,如果你想要覆写该方法,要确保调用了super的touchesBegan方法以传递任何你自身不处理的事件
  2. 如果你覆写该方法的时候没有调用super,那么你需要在你的自定义类中同时调用其他touches相关的方法,哪怕在这些方法中什么都不做。

事件拦截

UIEvent

  1. 它用于表示用户和APP的一个交互的对象。
  2. 永远不要retain一个UIEvent对象,或者是其内部的属性。如果的确需要retain某个UIEvent自带的属性,应对后者使用copy操作。
  3. 包含四个type: touches, motion, remote-Control, presses。motion是由UIKit触发的(要和Core Motion Framework的motion event区分开来。)remote-Control指的是用户通过外部配件(比如耳机、遥控器)对设备发出的操作指令。Press事件指的是用户通过游戏控制器、遥控器等的实体按键来和设备进行的交互行为。所有的这些可以通过UIEvent的type和subtype属性来加以判断。

接下来介绍一些重要的属性和方法:

func touches(for view: UIView) -> Set<UITouch>?

返回该事件中,属于指定view上的所有touch。

func touches(for window: UIWindow) -> Set<UITouch>?

和上面类似。

func touches(for gesture: UIGestureRecognizer) -> Set<UITouch>?

返回该手势识别器所接收到的所有UITouch对象。

func coalescedTouches(for touch: UITouch) -> [UITouch]?

这个方法是在iOS9之后提出的,它利用了一种叫做“触摸合并”的技术。由于系统对touch的采样在touchesMoved方法中进行,而后者的调用频率最高也才60次/秒(如果主线程有其他高耗时的操作,该方法的调用频率甚至更低),这样,就不可避免地会出现“漏点”的情况。而在新的iPad Pro2代上,界面刷新率达到了120Hz(使用Apple pencil时刷新率一度飙升至200Hz),因此,使用传统的touchesMoved必然会造成一个奇观:用户的手指在前面划线,画出来的线在后边追赶用户的手指……抑或是用户命名画了一条弧线,得到的却是一条“折线”……
基于此,苹果提出了触摸拟合技术,它可以让你获取到所有在两次touchesMoved调用之间的UITouch对象。
使用方法如下:
''override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)"
''{
'' if let coalescedTouches = event.coalescedTouchesForTouch(touch) {
'' print("coalescedTouches:", coalescedTouches.count)
''
'' for coalescedTouch in coalescedTouches
'' {
'' //Additional operations
'' }
''
'' }
'' }

`func predictedTouches(for touch: UITouch) -> [UITouch]?

同样是在iOS9以后,同样是为了减少延迟,苹果还推出了触摸预测技术,它根据先前触摸的点,使用一套非常精密的算法,来大致预测下一个被触摸的点所在的坐标。因此,开发者可以使用预测出来的点来提前做好UI更新的准备。
使用方法和func coalescedTouches(for touch: UITouch) -> [UITouch]?基本一样,在此不多赘述。

UITouch,UIEvent,UIResponder,UIGestureRecognizer的区别与联系

这一部分就算作是本文的小结了。我们来梳理一下四者的区别和联系:

上一篇 下一篇

猜你喜欢

热点阅读