iOS最常见的是触摸事件Touch Events。触摸事件除了是view来处理,还有高级的手势可以处理。所以,本文分别来讲讲触摸事件和手势,并结合例子讲讲两者混合使用的问题。
UITouch对象
一个手指第一次点击屏幕,就会生成一个UITouch对象,到手指离开时销毁。当我们有多个手指触摸屏幕时,会生成多个UITouch对象。UITouch对象可以表明触摸的位置、状态。
UIEvent对象
一个UIEvent对象代表iOS的一个事件。一个触摸事件定义为第一个手指开始触摸屏幕到最后一个手指离开屏幕。所以,一个UIEvent对象实际上对应多个UITouch对象。
响应链
响应链可以理解为一种虚拟的链表,每一个节点是一个UIResponder对象。UIResponder是事件接收与处理的基类,UIApplication、UIViewController和UIView都继承自UIResponder。UIResponder提供了几个事件处理的方法:
- (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;
UIResponder对象之间的联系靠nextResponder指针。
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
#else
- (nullable UIResponder*)nextResponder;
#endif
一个触摸事件到达真正处理它的对象时经过了一个搜索路径,这就是响应链的一部分。事件沿着这个响应链一直传递,直到碰到可以处理这个事件的UIResponder对象或者到达响应链的末尾(UIApplication)。
响应链的构造规则如下:
- 程序启动时,UIApplication会生成一个单例,并会关联一个APPDelegate。APPDelegate作为整个响应链的根建立起来,UIApplication的nextResponser为APPDelegate。
- 程序启动后,任何的UIWindow被创建时,UIWindow内部都会把nextResponser设置为UIApplication单例。
- UIWindow初始化rootViewController, rootViewController的nextResponser会设置为UIWindow。
- UIViewController初始化View,View的nextResponser会设置为rootViewController。
-
AddSubView时,如果subView不是ViewController的View,那么subView的nextResponser会被设置为superView。否则就是 subView -> subView.VC ->superView。
有了这个响应链,事件就可以按照这个路径逐级传递了。当前的对象不能处理这个事件,就交给nextResponser,一直到UIApplication单例。如果仍然不能处理,就丢弃。
Hit-Testing
当我们触摸屏幕时,到底应该由哪个对象最先响应这个事件呢?这就需要去探测,这个过程称为Hit-Testing,最后的结果称为hit-test view。涉及到两个方法是:
//先判断点是否在View内部,然后遍历subViews
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判断点是否在这个View内部
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
Hit-Testing是一个递归的过程,每一步监测触摸位置是否在当前view中,如果是,就递归监测subviews;否则,返回nil。
递归的根节点是UIWindow,对subviews的遍历顺序按照 后添加的先遍历 原则。
手势识别
手势是Apple提供的更高级的事件处理技术,可以完成更多更复杂的触摸事件,比如旋转、滑动、长按等。
手势绑定到一个View上,一个View上可以绑定多个手势。
UIGestureRecognizer同UIResponder一样也有四个方法:
//UIGestureRecognizer
- (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;
手势会在以上四个方法中去对手势的State做更改,手势的State表明当前手势是识别还是失败等等。比如单击手势会在touchesBegan 时记录点击位置,然后在touchesEnded判断点击次数、时间、是否移动过,最后得出否识别该手势。这几个方法一般在自定义手势里面使用。
手势识别与事件响应混用
1 触摸事件首先传递到手势上,如果手势识别成功,就会取消事件的继续传递,否则,事件会被响应链处理。具体地,系统维持了与响应链关联的所有手势,事件首先发给这些手势,然后再发给响应链。
2 在iOS 6.0 或以后版本中,默认控件操作方法会阻断重复手势的识别行为。
一个按钮的默认操作是一个单击。如果你有一个单击手势识别绑定到一个按钮的父视图上,然后用户点击该按钮,最后按钮的操作方法接收触摸事件而不是手势识别。 它只用于手势识别跟一个控件的默认操作重复时。
网友评论