UIResponder响应链

一、概述

  UIView与UIViewController的共同父类:UIResponder,对于点击touches一系列方法,UIView与UIViewController会做出一系列反应,下面从“如何找到点击的子view”和“如何根据响应链响应”两方面来认识UIResponder。

二、 如何找到点击的子view

  当用户点击某一个视图或者按钮的时候会首先响应application中UIWindow一层一层的向下查找,直到找到用户指定的view为止。

  比如上图,点击ViewE,会首先响应application中UIWindow一层一层的向下查找。查到ViewA,发现点击位置在ViewA内,接下来发现点击位置在ViewB内,接下来发现点击位置在ViewE内,就找到了ViewE。主要通过下面两个方法来找到的:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver‘s coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;   // default returns YES if point is in bounds

UIWindow会通过调用hitTest:withEvent:方法(这个方法会对UIWindow的所有子view调用pointInside:withEvent:方法,其中返回的YES的view为ViewA,得知用户点击的范围在ViewA中),类似地,在ViewA中调用hitTest:withEvent:方法,得知用户点击的范围在ViewB中,依此类推,最终找到点击的view为ViewE。

其中,hitTest:withEvent:方法大致的实现如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
     for (UIView *view in self.subviews) {
          if([view pointInside:point withEvent:event]){
              UIView *hitTestView = [view hitTest:point withEvent:event];
             if(nil == hitTestView){
                  return view;
              }
          }
      }
     return nil;
 }

通过以上这种递归的形式就能找到用户点击的是哪个view,其中还要注意的时当前的view是否开启了userIntercationEnabled属性,如果这个属性未开启,以上递归也会在未开启userIntercationEnabled属性的view层终止。

三、如何根据响应链响应

  既然找到了用户点击的view,那么当前就应该响应用户的点击事件了,UIView与UIViewController的共同父类是UIResponder,他们都可以复写下列4个方法:

-(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;

  这个响应点击事件的过程是上面的逆序操作,这就是用到了UIResponder的nextResponder方法了。比如上面的图点击ViewE,这时候ViewE先响应,接下来是nextResponder即ViewB,接下来是ViewB的nextResponder即ViewA。

  关于nextResponder有如下几条规则:

1. 当一个view调用其nextResponder会返回其superView;
2. 如果当前的view为UIViewController的view被添加到其他view上,那么调用nextResponder会返回当前的UIViewController,而这个UIViewController的nextResponder为view的superView;
3. 如果当前的UIViewController的view没有添加到任何其他view上,当前的UIViewController的nextResponder为nil,不管它是keyWinodw或UINavigationController的rootViewController,都是如此;
4. 如果当前application的keyWindow的rootViewController为UINavigationController(或UITabViewController),那么通过调用UINavigationController(或UITabViewController)的nextResponder得到keyWinodw;
5. keyWinodw的nextResponder为UIApplication,UIApplication的nextResponder为AppDelegate,AppDelegate的nextResponder为nil。

用图来表示,如下所示:

四、遇到的问题

  在开发过程中,我们有可能遇到UIScrollView 或 UIImageView 截获touch事件,导致touchesBegan: withEvent:/touchesMoved: withEvent:/touchesEnded: withEvent: 等方法不执行。比如下面这种情况,scrollView的superView是view,view对应的viewController中的touchesBegan: withEvent:/touchesMoved: withEvent:/touchesEnded: withEvent: 等方法就不执行。

如果想让viewController中的方法执行的话,你可能会提出下面的解决办法:

@implementation UIScrollView (Touch)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if([self isMemberOfClass:[UIScrollView class]]) {
        [[self nextResponder] touchesBegan:touches withEvent:event];
    }
}
@end

这样UIScrollView确实会用nextResponder把响应传递到view,接下来传递到viewController中。但是,如果没有使用if([self isMemberOfClass:[UIScrollView class]]) 进行过滤判断,那么,有可能会导致一个使用系统手写输入法时带来的crash问题。即手写的键盘的子view是UIKBCandidateCollectionView,调用了[[self nextResponder] touchesBegan:touches withEvent:event];后会造成系统的crash问题:

-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance 0x104f6c6b0

这个crash的复现见《UIKBBlurredKeyView candidateList:unrecognized...BUG修复》,它的解决办法也随处可见,比如《-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance 0x5a89960

此crash的技术层面详细原因:

手写的键盘的子view是UIKBCandidateCollectionView(UIColloectionView的子类)的实例,它的nextResponder是UIKBHandwritingCandidateView类型的实例,执行UIKBHandwritingCandidateView的touchesBegan:withEvent:方法后,会使得整个candidate view呈选中状态,而苹果对手写键盘的选择candidate字符时的原生处理方法是会避免candidate view呈选中状态的。整个candidate view呈选中状态后后再点击键盘的任意地方,本应调用UIKBCandidateView实例的方法candidateList,结果调用了UIKBBlurredKeyView的candidateList方法,导致方法找不到,导致"-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance "crash。

crash总结:

通过对这个crash的详细分析,虽然上面的使用isMemberOfClass判断后使用nextResponder对事件响应链进行传递没有问题,但由于nextResponder依然具有不可控性,还是不建议用category复写系统的方法,这一点以后一定注意。

谨慎使用Category,特别是覆盖系统原始方法的category的实现。

时间: 2024-10-10 04:38:28

UIResponder响应链的相关文章

ios中事件的响应链(Responder chain)和传递链

事件的响应链涉及到的一些概念 UIResponder类,是UIKIT中一个用于处理事件响应的基类.窗又上的所有事件触发,都由该类响应(即事件处理入又).所以,窗又上的View及控制器都是 派生于该类的,例如UIView.UIViewController等. 调用UIResponder类提供的方法或属性,我们就可以捕捉到窗又上的所有响应 事件,并进行处理. 响应者链条是由多个响应者对象连接起来的链条,其中响应者对象是能处理事 件的对象,所有的View和ViewController都是响应者对象,利

事件分发&amp;响应链

iOS的三种事件:触摸事件/运动事件/远程控制事件 [objc] view plaincopy typedef enum { UIEventTypeTouches, UIEventTypeMotion, UIEventTypeRemoteControl, } UIEventType; 只有继承UIResponder类的对象才能处理事件,如UIView.UIViewController.UIApplication都继承自UIResponder,都能接收并处理事件.UIResponder中定义了上面

什么是事件响应链

对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕.晃动设备.通过遥控设施控制设备.对应的事件类型有以下三种: 触屏事件(Touch Event) 运动事件(Motion Event) 远端控制事件(Remote-Control Event) 响应者链(Responder Chain) 响应者对象(Responder Object),指的是有响应和处理事件能力的对象.响应者链就是由一系列的响应者对象构成的一个层次结构. UIResponder是所有响应对象的基类,在UIRespond

iOS学习9_事件分发&amp;响应链&amp;android转iOS的感悟

iOS的三种事件:触摸事件/运动事件/远程控制事件 typedef enum { UIEventTypeTouches, UIEventTypeMotion, UIEventTypeRemoteControl, } UIEventType; 只有继承UIResponder类的对象才能处理事件,如UIView.UIViewController.UIApplication都继承自UIResponder,都能接收并处理事件.UIResponder中定义了上面三类事件相关的处理方法: 下面主要讨论触摸事

iOS开发 - 事件传递响应链

一.序言 当我们在使用微信等工具,点击扫一扫,就能打开二维码扫描视图.在我们点击屏幕的时候,iphone OS获取到了用户进行了"单击"这一行为,操作系统把包含这些点击事件的信息包装成UITouch和UIEvent形式的实例,然后找到当前运行的程序,逐级寻找能够响应这个事件的对象,直到没有响应者响应.这一寻找的过程,被称作事件的响应链,如下图所示,不用的响应者以链式的方式寻找. 事件响应链: 二.响应者 在iOS中,能够响应事件的对象都是UIResponder的子类对象.UIRespo

iOS事件响应链

@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css); 事件传递之响应链 当你设计App时你可能需要动态的响应事件.例如,一个触摸事件可能发生在屏幕上不同的对象中,你需要决定哪个对象来响应这个给定的事件,理解对象如何接收事件. 当用户触发的一个事件发生,UIKit会创建一个包含要处理的事件信息的事件对象.然后她会将事件对象

iOS开发——响应链(Responder Chain)的深入理解和代码示例

我在之前一篇博客<iOS响应者链Responder Chain浅析>中对iOS开发中遇到的响应者链概念有了基本的了解.但是仅仅停留在理解概念的基础上还是远远不够的.该博客我们会通过代码案例来深入理解响应链.该博客的示例上传至 https://github.com/chenyufeng1991/ResponderChain  . (1)首先来说说第一响应者(First Responder).响应事件的传递过程就是为了找到第一响应者.以下几个方法: isFirstResponder:判断该View

iOS 响应链

iOS 响应链 原文  http://blog.csdn.net/yadong000/article/details/8837241 首先要明确的是:在IOS中,有响应者链对事件进行响应,所有的响应类都是UIResponder的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会. 响应链的过程: 当事件发生的时候,响应链首先被发送给第一个响应者(往往是事件发生的视图,也就是用户触摸屏幕的地方).事件将沿着响应者链一直向下传递,知道被接受并作出处理.一般来说,

事件处理指南(Event Handling Guide for iOS) 阅读笔记 (二) 响应链

Event Delivery: The Responder Chain 我们希望在我们的app中可以动态的响应触摸事件.比如一个触摸可能会发生在屏幕上不同的位置和不同的组件上, 我们需要判断哪个组件响应这个触摸并且了解这个组件是如何接受到触摸事件的. 当一个用户触摸事件发生了, UIKit会创建一个包含需要被处理的事件信息的对象.然后将这个对象放入当前的事件循环队列中,对于触摸事件,这个对象被创建为 UIEvent 对象,对于移动事件这个对象会依赖于你使用的 Framework和你所关心的移动事