触屏事件(Touch Event)
UIResponder
阅读前请先了解一下以下2个方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
首先通过一个例子来了解一下UIResponder事件分发和响应者链的过程(用户点击视图View E)。
1.事件分发
(1)iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理
(2)A是UIWindow的根视图,因此,UIWindwo对象会首相对A进行hit-test,显然用户点击的范围是在A的范围内,因此,pointInside:withEvent:返回了YES,这时会继续检查A的子视图
(3)子视图B点击的范围不再B内,因此B分支的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;子视图C点击的范围在C内,即C的pointInside:withEvent:返回YES
(4)子视图D点击的范围不再D内,因此D的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;子视图E点击的范围在E内,即E的pointInside:withEvent:返回YES,由于E没有子视图(也可以理解成对E的子视图进行hit-test时返回了nil),因此,E的hitTest:withEvent:会将E返回,再往回回溯,就是C的hitTest:withEvent:返回E--->>A的hitTest:withEvent:返回E。
至此,本次点击事件的第一响应者就通过响应者链的事件分发逻辑成功的找到了。
补充说明
hitTest:withEvent:方法将会忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds 属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。
2.响应者链
当发生事件响应时,必须知道由谁来响应事件。在 iOS 中,由响应者链来对事件进行响应。
(1)所有事件响应的类都是 UIResponder 的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。当发生事件时,事件首先被发送给第一响应者,第一响应者则是通过响应者链的事件分发逻辑来确定的。
(2)如果第一响应者无法响应事件或者hit-test没有找到第一响应者,事件会随着响应链向上回溯,回溯顺序如图,如果UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃
(3)一般来说,第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象 ViewController(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow 对象)再到程序(UIApplication 对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。
UIGestureRecognizer
假设如果没有手势,如果我们需要监听视图的手势则需要通过这四个方法
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event;
通过这些方法监听用户手势行为有明显的缺点
(1)需要自定义视图
(2)默认情况,外界不知道监听的触摸事件情况
(3)不容易规范的区分具体手势行为
因此苹果推出了一系列的手势行为,来简化开发难度
现在来讨论一下,UIGestureRecognizer和UIResponder的优先级
如果在视图A上添加一个Tap手势,此时点击视图E,则会发现并没有响应视图E的action而是执行了视图A上Tap手势的action
我们会发现手势(2)的优先级比所绑定的视图(3)的优先级高
到这里我们不妨再推敲一下,为什么要这样设计这个机制呢?不能等view判断自己能否处理之后再往下传递么?
答:如果是父view是缩放手势,如果按照依次传递会怎么样?可以看出在处理的时效性可准确性方面不如这么设计好.
那苹果的文档在hit-test说的就是最上面的view处理不了再交给后面的view啊?这不矛盾么?
答:这不矛盾,我们看文档不能断章取义,不能太机械,苹果在hit-test中说的是一种宏观上的表现形式.
hit-test的目标就是抓住touch对应的响应者链的头,这样我们就可以分发了,不然我们如何高效去分发呢?
参考资料
https://hit-alibaba.github.io/interview/iOS/Cocoa-Touch/Event-Handling.html
http://blog.csdn.net/zhoupengju/article/details/52250135