一.UIEvent,UITouch在触摸处理中的作用
在iOS系统中,当用户手指开始接触屏幕到所有手指都离开屏幕,这整个过程叫做一个multitouch sequence.这个过程可能有多点触碰。
而在这整个过程中,系统需要一个对象来存储相关的信息,这个对象就是UIEvent。一旦一个新的触摸过程开始,一个新的UIEvent对象就被建立,并且随着触摸状态的改变不断更新自己的信息,一个完整的触摸过程对应一个UIEvent的实例。
那UITouch又是什么呢?前面提到过,在一次触摸过程中,可能有多个手指在触摸,即所谓的多点触控,而一个UITouch的实例则对应一个手指的触摸过程,当UITouch的实例所对应的手指的触摸状态发生改变时,UITouch的状态就会被更新。因为一个触摸过程可能是多点触碰的,因此一个UIEvent实例会有多个UITouch
open var allTouches: Set<UITouch>? { get }
总结:
- 从手指开始触摸到离开屏幕的整个过程叫做一次multitouch sequence。
- 一次multitouch sequence对应一个UIEvent对象。
- 一个手指的触摸过程对应一个UITouch对象
- 一个UIEvent的对象可能含有多个UITouch对象(多点触控)
二.在view中利用UITouch进行触摸处理
那么iOS是如何对触摸进行相应的呢?
首先先简单介绍一下UITouch的四种状态(UITouchPhase)
public enum UITouchPhase : Int {
case began // finger开始接触
case moved // finger正在移动
case stationary // finger处于静止状态
case ended // finger离开
case cancelled // 触摸并没有结束但有其他状况发生导致触摸取消(比如按下了home键)
}
在iOS中,对于触摸的处理是通过UIResponder这个类来实现的,这是一个抽象类,UIView继承自这个类,因此实际上我们所有的触摸事件的处理是通过view来进行的。view是处理触摸的基本单位
因此,实际上每个UITouch实例都有一个view属性,代表持有它的view。view通过以下四个方法来处理触摸事件:
//当一个touch开始时调用此方法)
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
//当一个touch移动时调用次方法
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
//当一次touch结束时调用此方法
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
//当一个touch取消时调用此方法
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
在上面四个方法的参数中:
- touches表示这个view中的所有touch(可能有多个手指在触摸一个view)
- event代表整个的触摸过程(除了这个view,可能其他的view中也有touches发生,event代表了整个触摸事件)
因此,你可以在view中实现这四个方法,从而就可以达到处理触摸的效果。例如,你如果想要识别用户的长按手势,你只需要在touchBegan这个方法中记录下touch开始的时间,然后在touchMoved方法中记录与开始时间的间隔,如果这个时间间隔大于某个值,你即可以认为这是一次长按过程,然后进行相应的处理。
总结:
- UITouch有begin,moved等5种状态
- UIVIew是处理touch的基本单位
- 系统通过向UIVIew发送touchesBegin的消息来通知触摸状态的改变
- 可以在UIView中实现touchesBegin等四个方法来实现自定义的处理
三.UIGestureRecognizer的由来及其与UITouch和UIEvent之间的关系
以上谈论的都是iOS比较底层的处理过程,在实际开发中,我们可能根本不会接触到这个东西的,我们都知道,我们实际上是利用来UIGestureRecognizer处理用户的的触摸事件的,那么,UIGestureRecognizer和以上谈论的UITouch和UIEvent以及UIResponder之间有什么关系呢?
为什么会有UIGestureRecognizer
通过上一节的讨论我们知道,如果我们想要识别用户的长按手势,这意味的我们需要定义一个新类继承UIView,然后在这个新类中实现touchesBegen等方法来进行手势识别,然后再来进行相应的处理,这个过程实际上是非常繁琐的,有没有一种便捷的方法呢?
为此,iOS引入了UIGestureRecognizer,将一些常用的手势封装好,(例如UITapGestureRecognizer),然后我们就可以在更高的层面上来进行处理了。
UIGestureRecognizer的工作原理
我们通常在使用UIGestureRecognizer的时候,通常是将它直接加到一个view上去,例如这样:
let tap = UITapGestureRecognizer(target: self, action: "handleTap:")
self.view.addGestureRecognizer(tap)
实际上UIGestureRecognizer的工作原理很简单,它和UIView一样,也实现了touchesBegin等四个方法,借此实现了自己的手势识别过程,当它识别成功时,就通知其target进行相应的处理。一个GestureRecognizer的地位实际上和view的地位是对等的,有UITouch和UIEvent有相同的处理方式
touch的传递过程
- 当一次touch开始时,会同时传递给(理解这点很重要)相应的view以及view上的GestureRecognizer(如果有的话)
- 如果一个GestureRecognizer识别到了它的Gesture的话,那么
- touchesCancelled:forEvent:会发送给相应的view,touch不再传递给这个view
- 如果这个view上还有其他手势的话,这些手势会forced to fail.(这个是为了保证一次只能有一个手势,避免手势冲突)
- 如果 a gesture recognizer 识别失败,那么touches不再传递给它,但还是会传递给相应的view
- 如果view没有实现相应的touches方法的话,那么该事件会沿着响应者链继续传递给下一个相应者处理(比如subview没有实现相应的touches方法,则会调用superview相应的touches方法)
总结:
- GestureRecognizer和view一样,实现了touchesBegin等四个方法,由此实现了手势识别的过程
- 当touch开始的时候,是会同时传递给view和view上的GestureRecognizer的,它们会同时调用自己touches方法进行处理
- 当GestureRecognizer识别成功时,touch便不再传递给view,同时view上的其他GestureRecognizers也会forced to fail
- 如果 a gesture recognizer 识别失败,那么touches不再传递给它,但还是会传递给相应的view
- 如果view没有实现touches方法,则该事件会沿着响应者链进行传递