UITouch/UIResponder:iOS上触摸事件的视图检测和事件传递

来源:互联网 发布:软件工程项目明细 编辑:程序博客网 时间:2024/06/05 13:26

iPhone上有非常流畅的用户触摸交互体验,能检测各种手势:点击,滑动,放大缩小,旋转。大多数情况都是用UI*GestureRecognizer这样的手势对象来关联手势事件和手势处理函数。也有时候,会看到第三方代码里会在如下函数中进行处理:

-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;

那么问题就来了,手势和touch到底有什么区别和联系?这一切还得从头iOS触摸事件检测,以及UIResponder(响应者)开始说起。

开始前,请大家先看一下Apple的官方说明:https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/event_delivery_responder_chain/event_delivery_responder_chain.html#//apple_ref/doc/uid/TP40009541-CH4-SW2

很幸运的是,有人将它翻译成中文了:http://blog.csdn.net/chun799/article/details/8223612

以下是我的总结,我建议大家先看完官方说明,自己先琢磨过,有了自己的体会,再来看我的总体。我的总结起来就2点:第一步,当我们触摸了屏幕,iOS是如何找到我们触摸哪个视图。第二步,当确定了触摸的视图,又如何传递和处理触摸事件。

第一步 寻找发生触摸的视图

检测的目的是为了找到触摸是发生在哪个视图上(UIView)。这个检测的顺序是从底向上的检测过程。首先UIApplication会传递给UIWindow,然后再由UIWindow传递给顶级的视图,顶级视图会进一步遍历其所有的subviews。UIView有个函数叫hitTest,如果触摸事件是发生在该视图中,则该函数会返回非空UIView;然后该视图递归其subviews,最后发现最终的subview。

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event

-这是一个从底向上的过程:从UIApplication->UIWindow

->顶级视图->下级视图;

-这是一个遍历和递归的过程:遍历所有的subview,递归调用subview的subview。

-这是一个寻找确定hit-test视图的过程:我们把最终找到的视图成作为:hit-test view。

这里有个特殊情况,如果一个视图被设置为user-interaction = NO,那么hitTest会返回空指针。某些继承自uiview的组件默认user-interaction = NO,比方说UIImageView。

再考虑一个情况,如果有个视图盖在另一个视图之上,触摸发生在了它们的交界区域,最终检测结果会是什么?经过测试,发现顶层视图响应了触摸事件。这个事可以这么来解释,虽然它们同属于一个uiview的子视图,当父视图循环其子视图,一定会先从最上层的子视图开始。所以,被覆盖的视图没有机会被调用hitTest。
第二步 传递和处理触摸事件:UIResponder(响应者)

从UIResponder的头文件来看,它不仅能处理触摸事件,还能处理手机移动事件(比方晃动手机),并处理远程事件。这里我们只看触摸事件:

–touchesBegan:withEvent:– touchesMoved:withEvent:– touchesEnded:withEvent:– touchesCancelled:withEvent:

UIView继承自UIResponder;UIWindow继承自UIView,因此它也是UIResponder的子类。

第一步骤结束一定能找一个hit-test view,然后就开始调用其UIResponder->touches*函数进行处理;如果我们不重写touches函数的话,默认会调用self->nextResponder->touches*进一步把触摸事件往下进行传递。每个responder都有个nextResponder,这就组成了一个响应者链(responder

chain)。值得注意的是:

1,如果我们重写touches*函数时,不调用[super touches*]的话,那么事件就不会继续往下传递。

2,UIViewController也继承自UIResponder,所以响应链中除了有视图,也有视图控制器。所以视图控制区中,也可以实现touches*函数。

3,关于响应链的形成:看起来响应链是非常个错综复杂的数据链,其实它很简单。每个responder只关心其nextResponder就可以了。而关于nextResponder的赋值过程我推测是这样的:当uiviewcontroller初始化是,将其关联的视图的nextResponder设为自己;当一个子视图add时,就自动将其nextResponder设为其父视图。
UI*GestureRecognizer,UIControl和touches的关系

UITouch是底层的触摸操作,如果开发人要将其解释/识别为各种手势操作,那需要太多工作量。而有了UI*GestureRecognizer的帮助,我们就不需要关心touches*函数了。这里面还有细节,请看https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html#//apple_ref/doc/uid/TP40009541-CH2-SW2。

UIControl是同样的道理,它其实帮助我们脱离太过底层的触摸操作,而帮我们直接解释为按钮按下,抬起等等动作。而且,可见UIControl不会将触摸事件进一步再往下传递。
使用UITouch/UIResponder实现一些黑魔法

1,找uiview所属的视图控制器:

@implementation UIView (ParentController)-(UIViewController*)parentController{UIResponder *responder = [self nextResponder];while (responder) {if ([responder isKindOfClass:[UIViewController class]]) {return (UIViewController*)responder;}responder = [responder nextResponder];}return nil;}@end

2,操纵响应触摸事件的视图

- (UIView*)hitTest:(CGPoint)pointwithEvent:(UIEvent*)event{if(!CGRectContainsPoint(self.pageScrollView.frame, point)) {returnself.pageScrollView;}return[superhitTest:pointwithEvent:event];}

3,操作响应链

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {if(!self.dragging) {[self.nextRespondertouchesBegan:toucheswithEvent:event];}else{[supertouchesBegan:toucheswithEvent:event];}}

重要的事情再说一次:

第一步 寻找发生触摸的视图第二步 传递和处理触摸事件:UIResponder(响应者)

请反复体会,无论是谁都替代不了你的自我思考。

学的越多,做的越快;学的越多,做的越好。

0 0