欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

(iOS)响应事件传递, nextResponder研究

来源: 开发者 投稿于  被查看 25884 次 评论:67

(iOS)响应事件传递, nextResponder研究


响应事件传递, nextResponder研究

这里,我们考虑以下二种情况。

 

问题1。 如何调用父view的controller里面的方法?

答案如下:
[[self superview ].nextResponder method];
[[[self superview ] nextResponder] method];
[self.nextResponder method];
上面的都可以,看情况使用,使用的时候最好判断一下。

官方解释
UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t); UIViewController implements the method by returning its view’s superview; UIWindow returns the application object, and UIApplication returns nil.

即如下代码可以进行判断:

id next = [self nextResponder];
while(![next isKindOfClass:[ViewController class]])//这里跳不出来。。。有人说这里跳不出来,其实是因为它没有当前这个view放入ViewController中,自然也就跳不出来了,会死循环,使用时需要注意。
{
next = [next nextResponder];
}
if ([next isKindOfClass:[ViewController class]])
{
controller = (ViewController *)next;
}


问题2:当一个子view需要接收点击事件,而父view也需要接收点击事件, 如何做?

当然, 你可能会说直接调用mysubview.superView即可, 这样做也确实是可以做到,但有时子view是不一定知道有这个特定的父view的存在的,如动态添加子view。

所以这里就可以用到消息响应链拉技术。

下面要做的也就是,让子view接收这些事件后,同时把这些事件继续向上传,会一直传到UIApplication为止。 而在传的过程中,如果子view接收了这些事件,那么事件会自然终止,我们现在可以做的是同时让子view接收事件,而且还让事件不终止,并继续向上传。

摘取一部分说明:

当用户 与 iPhone的触摸屏 产生 互动时,硬件 就会探测到 物理接触 并且 通知 操作系统。接着 操作系统 就会创建 相应的事件 并且 将 其 传递给 当前正在运行的应用程序的事件队列。然后 这项事件 会被事件循环 传递给 优先响应者物件。优先响应者物件 是 事件 被触发时 和 用户 交互的物件,比如 按钮物件、视图物件。如果 我们 编写了 代码 让 优先响应者 处理 这种类型的事件,那么 它 就会处理 这种类型的事件。处理完 某项事件后,响应者 有 两个选项:1、将 其 丢弃;2、将 其 传递给 响应链条中的下一个响应者。下一个响应者的地址 存储 在当前响应者物件所包含的变量nextResponder当中。如果 优先响应者 无法处理 一项事件,那么 这项事件 就传递给 下一个响应者,直到 这项事件 到达 能处理它的响应者 或者 到达 响应链条的末端,也就是 UIApplication类型的物件。UIApplication类型的物件 收到 一项事件后,也是 要么 处理,要么 丢弃。

比如 有 一个视图物件,这个视图物件上 有 一个按钮物件。当用户 触摸 这个按钮物件时,作为优先响应者,这个按钮物件 就会收到 一项事件。如果 这个按钮物件 无法处理 这项事件,就会将 这项事件 传递给 视图物件。如果 视图物件 无法处理 这项事件,就会将 这项事件 传递给 视图控制器物件。以此类推。

应该注意的 是 当我们 在使用 响应链条时,一项事件 并不会自动地 从一个响应者 传递到 下一个响应者。如果 要将 一项事件 从一个响应者 传递到 下一个响应者,我们 必须编写 代码 才能办到。

要做的如下:

view的代码如下:

 

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

{

// 这里可以做子view自己想做的事,做完后,事件继续上传,就可以让其父类,甚至父viewcontroller获取到这个事件了

[[selfnextResponder]touchesBegan:toucheswithEvent:event];

}

 

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

{

[[selfnextResponder]touchesEnded:toucheswithEvent:event];

}

 

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

{

[[selfnextResponder] touchesCancelled:toucheswithEvent:event];

}

 

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

{

[[selfnextResponder] touchesMoved:toucheswithEvent:event];

}

另外需要注意的是:在重写这几个方法时,最好保证这几个方法都重写,否则事件响应链可能会变混乱。这是我的猜测哈,没有实际验证过。

一.responder对象

在iOS系统中,能够响应并处理事件的对象称之为responder object, UIResponder是所有responder对象的基类,在UIResponder类中定义了处理各种事件,包括触摸事件(Touch Event)、运动事件(Motion Event)和远程控制事件(Remote-Control Events)的编程接口,其中处理触摸事件(Touch Event)的编程接口如下:
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
– touchesCancelled:withEvent:
这四个方法分别处理触摸开始事件,触摸移动事件,触摸终止事件,以及触摸跟踪取消事件。

UIApplication, UIViewController,UIView和所有继承自UIView的UIKit类(包括UIWindow,继承自UIView)都直接或间接的继承自UIResponder,所以它们的实例都是responder object对象,都实现了上述4个方法。UIResponder中的默认实现是什么都不做,但UIKit中UIResponder的直接子类(UIView,UIViewController…)的默认实现是将事件沿着responder chain继续向上传递到下一个responder,即nextResponder。所以在定制UIView子类的上述事件处理方法时,如果需要将事件传递给next responder,可以直接调用super的对应事件处理方法,super的对应方法将事件传递给next responder,即使用

[super touchesBegan:touches withEvent:event];

不建议直接向nextResponder发送消息,这样可能会漏掉父类对这一事件的其他处理。

[self.nextResponder  touchesBegan:touches withEvent:event];

 

另外,在定制UIView子类的事件处理方法时,如果其中一个方法没有调用super的对应方法,则其他方法也需要重写,不使用super的方法,否则事件处理流程会很混乱。

注:UIKit框架的类层次结构图见:UIKit Framework Reference

二.responder chain

上文提到了responder chain,responder chain是一系列连接的responder对象,通过responder对象可以将处理事件的责任传递给下一个,更高级的对象,即当前responder对象的nextResponder。
iOS中responder chain的结构为:
\

  • UIView的nextResponder属性,如果有管理此view的UIViewController对象,则为此UIViewController对象;否则nextResponder即为其superview。
  • UIViewController的nextResponder属性为其管理view的superview.
  • UIWindow的nextResponder属性为UIApplication对象。
  • UIApplication的nextResponder属性为nil。

    iOS系统在处理事件时,通过UIApplication对象和每个UIWindow对象的sendEvent:方法将事件分发给具体处理此事件的responder对象(对于触摸事件为hit-test view,其他事件为first responder),当具体处理此事件的responder不处理此事件时,可以通过responder chain交给上一级处理。

    1. 如果hit-test view或first responder不处理此事件,则将事件传递给其nextResponder处理,若有UIViewController对象则传递给UIViewController,传递给其superView。
    2. 如果view的viewController也不处理事件,则viewController将事件传递给其管理view的superView。
    3. 视图层级结构的顶级为UIWindow对象,如果window仍不处理此事件,传递给UIApplication.
    4. 若UIApplication对象不处理此事件,则事件被丢弃。

      三.巧妙利用nextResponder

      通过UIViewController的view属性可以访问到其管理的view对象,及此view的所有subviews。但是根据一个view对象,没有直接的方法可以得到管理它的viewController,但我们使用responder chain可以间接的得到,代码如下:

      @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
      
      
      
      
      
      
      
      

      iOS事件机制(一)

      DEC 7TH, 2013

      运用的前提是掌握 掌握的本质是理解

      \

      本篇内容将围绕iOS中事件及其传递机制进行学习和分析。在iOS中,事件分为三类:

      • 触控事件(单点、多点触控以及各种手势操作)
      • 传感器事件(重力、加速度传感器等)
      • 远程控制事件(远程遥控iOS设备多媒体播放等)

        这三类事件共同构成了iOS设备丰富的操作方式和使用体验,本次就首先来针对第一类事件:触控事件,进行学习和分析。

        Gesture Recognizers

        Gesture Recognizers是一类手势识别器对象,它可以附属在你指定的View上,并且为其设定指定的手势操作,例如是点击、滑动或者是拖拽。当触控事件 发生时,设置了Gesture Recognizers的View会先通过识别器去拦截触控事件,如果该触控事件是事先为View设定的触控监听事件,那么Gesture Recognizers将会发送动作消息给目标处理对象,目标处理对象则对这次触控事件进行处理,先看看如下流程图。

        \

        在iOS中,View就是我们在屏幕上看到的各种UI控件,当一个触控事件发生时,Gesture Recognizers会先获取到指定的事件,然后发送动作消息(action message)给目标对象(target),目标对象就是ViewController,在ViewController中通过事件方法完成对该事件的处理。Gesture Recognizers能设置诸如单击、滑动、拖拽等事件,通过Action-Target这种设计模式,好处是能动态为View添加各种事件监听,而不用去实现一个View的子类去完成这些功能。

        以上过程就是我们在开发中在方法中常见的设置action和设置target,例如为UIButton设置监听事件等。

        常用手势识别类

        在UIKit框架中,系统为我们事先定义好了一些常用的手势识别器,包括点击、双指缩放、拖拽、滑动、旋转以及长按。通过这些手势识别器我们可以构造丰富的操作方式。

        \

        在上表中可以看到,UIKit框架中已经提供了诸如UITapGestureRecognizer在内的六种手势识别器,如果你需要实现自定义的手势识别器,也可以通过继承UIGestureRecognizer类并重写其中的方法来完成,这里我们就不详细讨论了。

        每一个Gesture Recognizer关联一个View,但是一个View可以关联多个Gesture Recognizer,因为一个View可能还能响应多种触控操作方式。当一个触控事件发生时,Gesture Recognizer接收一个动作消息要先于View本身,结果就是Gesture Recognizer作为View处理触控事件的代表,或者叫代理。当Gesture Recognizer接收到指定的事件时,它就会发送一条动作消息(action message)给ViewController并处理。

        连续和不连续动作

        \

        触控动作同时分为连续动作(continuous)和不连续动作(discrete),连续动作例如滑动和拖拽,它会持续一小段时间,而不连续动作例如单击,它瞬间就会完成,在这两类事件的处理上又稍有不同。对于不连续动作,Gesture Recognizer只会给ViewContoller发送一个单一的动作消息(action message),而对于连续动作,Gesture Recognizer会发送多条动作消息给ViewController,直到所有的事件都结束。

        为一个View添加GestureRecognizer有两种方式,一种是通过InterfaceBuilder实现,另一种就是通过代码实现,我们看看通过代码来如何实现。

        MyViewContoller.m
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        
        - (void)viewDidLoad {
             [super viewDidLoad];
        
             // 创建并初始化手势对象
             UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
                  initWithTarget:self action:@selector(respondToTapGesture:)];
        
             // 指定操作为单击一次
             tapRecognizer.numberOfTapsRequired = 1;
        
             // 为当前View添加GestureRecognizer
             [self.view addGestureRecognizer:tapRecognizer];
        
             // ...
        }
        

        通过上述代码,我们实现了为当前MyViewController的View添加一个单击事件,首先构造了UITapGestureRecognizer对象,指定了target为当前ViewController本身,action就是后面自己实现的处理方法,这里就呼应了前文提到的Action-Target模式。

        在事件处理过程中,这两种方式所处的状态又各有不同,首先,所有的触控事件最开始都是处于可用状态(Possible),对应UIKit里面的UIGestureRecognizerStatePossible类,如果是不连续动作事件,则状态只会从Possible转变为已识别状态(Recognized,UIGestureRecognizerStateRecognized)或者是失败状态(Failed,UIGestureRecognizerStateFailed)。例如一次成功的单击动作,就对应了Possible-Recognized这个过程。

        \

        如果是连续动作事件,如果事件没有失败并且连续动作的第一个动作被成功识别(Recognized),则从Possible状态转移到Began(UIGestureRecognizerStateBegan)状态,这里表示连续动作的开始,接着会转变为Changed(UIGestureRecognizerStateChanged)状态,在这个状态下会不断循环的处理连续动作,直到动作执行完成变转变为Recognized已识别状态,最终该动作会处于完成状态(UIGestureRecognizerStateEnded),另外,连续动作事件的处理状态会从Changed状态转变为Canceled(UIGestureRecognizerStateCancelled)状态,原因是识别器认为当前的动作已经不匹配当初对事件的设定了。每个动作状态的变化,Gesture Recognizer都会发送消息(action message)给Target,也就是ViewController,它可以根据这些动作消息进行相应的处理。例如一次成功的滑动手势动作就包括按下、移动、抬起的过程,分别对应了Possible-Began-Changed-Recognized这个过程。

        UITouch & UIEvent

        在屏幕上的每一次动作事件都是一次Touch,在iOS中用UITouch对象表示每一次的触控,多个Touch组成一次Event,用UIEvent来表示一次事件对象。

        \

        在上述过程中,完成了一次双指缩放的事件动作,每一次手指状态的变化都对应事件动作处理过程中得一个阶段。通过Began-Moved-Ended这几个阶段的动作(Touch)共同构成了一次事件(Event)。在事件响应对象UIResponder中有对应的方法来分别处理这几个阶段的事件。

        • touchesBegan:withEvent:
        • touchesMoved:withEvent:
        • touchesEnded:withEvent:
        • touchesCancelled:withEvent:

          后面的参数分别对应UITouchPhaseBegan、UITouchPhaseMoved、UITouchPhaseEnded、UITouchPhaseCancelled这几个类。用来表示不同阶段的状态。

          事件传递

          \

          如上图,iOS中事件传递首先从App(UIApplication)开始,接着传递到Window(UIWindow),在接着往下传递到View之前,Window会将事件交给GestureRecognizer,如果在此期间,GestureRecognizer识别了传递过来的事件,则该事件将不会继续传递到View去,而是像我们之前说的那样交给Target(ViewController)进行处理。

          响应者链(Responder Chain)

          通常,一个iOS应用中,在一块屏幕上通常有很多的UI控件,也就是有很多的View,那么当一个事件发生时,如何来确定是哪个View响应了这个事件呢,接下来我们就一起来看看。

          响应者对象(Responsder Object)

          响应者对象是能够响应并且处理事件的对象,UIResponder是所有响应者对象的父类,包括UIApplication、UIView和UIViewController都是UIResponder的子类。也就意味着所有的View和ViewController都是响应者对象。

          第一响应者(First Responder)

          第一响应者是第一个接收事件的View对象,我们在Xcode的Interface Builder画视图时,可以看到视图结构中就有First Responder。

          \

          这里的First Responder就是UIApplication了。另外,我们可以控制一个View让其成为First Responder,通过实现 canBecomeFirstResponder方法并返回YES可以使当前View成为第一响应者,或者调用View的becomeFirstResponder方法也可以,例如当UITextField调用该方法时会弹出键盘进行输入,此时输入框控件就是第一响应者。

          事件传递机制

          如上所说,,如果hit-test view不能处理当前事件,那么事件将会沿着响应者链(Responder Chain)进行传递,知道遇到能处理该事件的响应者(Responsder Object)。通过下图,我们来看看两种不同情况下得事件传递机制。

用户评论