1.简介

   测试代码库:https://github.com/xufeng79x/EventHandler

  响应者链是系统寻找事件相应者的一个路径,他是同touch事件的Hit-testing过程具有同等地位的事件寻主过程。

此路径开始于firstResponder结束于单例application。事件首先会让firstResponder对象去处理,如果他无法处理则会向其nextResponder对象转发事件

当所有对象都无法处理事件后将最后转发到application处并最终忽略该事件。

2.响应者链的结构

响应者链中的对象必须都是继承自UIResponder的,在UIResponder类中如下属性描绘着响应者链结构信息:

Managing the Responder Chain

   就像C语言的链表结构一样,他通过一个属性nextResponder链接下一个事件处理对象,我们也可以看到第一响应者是可以通过自身becomeFirstResponder方法进行注册的,当然前期是当前对象可以成为第一响应者(canBecomeFirstResponder返回需要为YES)
 
3.如何成为第一响应者:
第一相应者是被定义为首先处理事件的对象,通常而言第一次响应者为一个view对象,可以通过如下方法来注册成为第一响应者:
1.覆盖 canBecomeFirstResponder 方法来返回YES.
2.实现becomeFirstResponder 方法. 如果可以,需要可以自身能够进行调用。
 
??需要注意的是:becomeFirstResponder等方法必须要在响应者链的确定后才能调用,比如能够在viewDidAppear:方法中调用起效,而不能再viewWillAppear:中调用。
 
3.如果你想要一个UIView或者其子类能够成为第一响应者,那么需要覆盖 canBecomeFirstResponder 方法来返回YES.但是并不需要去覆盖becomeFirstResponder方法。当然
如果你醒目的告知用户当前谁在响应事件(比如高亮区域等),那么你可以去覆盖它,并在调用父类becomeFirstResponder成功的情况下去做一些特别的效果:
if ([super becomeFirstResponder])
{
       // 其他功能代码
}
 
4.响应者链的输入事件
那么什么事件会传递给响应者链来进行处理呢?除了触摸事件由hit-testing过程来寻主外,系统也会产生和接受其他类型的事件,这些事件将有响应者链来进行寻主,事件包括:
  • Touch events. If the hit-test view cannot handle a touch event, the event is passed up a chain of responders that starts with the hit-test view.

  • --》触摸事件。如果hit-test view没有处理触摸事件,则这些事件将从以hit-test view为起点的响应者链进行处理。
  • Motion events. To handle shake-motion events with UIKit, the first responder must implement either the motionBegan:withEvent: or motionEnded:withEvent: method of the UIResponder class, as described in Detecting Shake-Motion Events with UIEvent.

  • --》运动事件。如果firstResponder要想处理运动事件,则必须实现 motionBegan:withEvent:或者motionEnded:withEvent:方法,详细说明见Detecting Shake-Motion Events with UIEvent.
  • Remote control events. To handle remote control events, the first responder must implement the remoteControlReceivedWithEvent: method of the UIResponder class.

  • --》远程事件。为了处理远程事件,firstResponder必须实现remoteControlReceivedWithEvent:方法。
  • Action messages. When the user manipulates a control, such as a button or switch, and the target for the action method is nil, the message is sent through a chain of responders starting with the control view.

  • --》功能消息。当用户操作一个control控件,比如按钮或者开关控件,如果为设置对应的action处理方法,那么这些事件将会被传递给以当前control控件为起点的响应者链去处理。
  • Editing-menu messages. When a user taps the commands of the editing menu, iOS uses a responder chain to find an object that implements the necessary methods (such as cut:copy:, and paste:). For more information, see Displaying and Managing the Edit Menuand the sample code project, CopyPasteTile.

  • --》编辑菜单选择消息。当用户触发了编辑菜单命令,IOS通过响应者链去发现实现了必要方法的对象(诸如:cut:,copy:和paste:)。详细可参考Displaying and Managing the Edit Menu和样例代码工程CopyPasteTile
  • Text editing. When a user taps a text field or a text view, that view automatically becomes the first responder. By default, the virtual keyboard appears and the text field or text view becomes the focus of editing. You can display a custom input view instead of the keyboard if it’s appropriate for your app. You can also add a custom input view to any responder object. For more information, see Custom Views for Data Input.

  • --》文本编辑。当用户去点击文本编辑区域或者显示文本输入控件,这个控件就自动变成了firstResponder。默认地,虚拟键盘将会弹出。你可以使用自定义的输入方式来取代默认键盘。详细可参考Custom Views for Data Input

UIKit框架自动帮助我们将文本控件设置为firstResponder,但是对于其他对象来说需要去明确地实现becomeFirstResponder方法等。

5.响应者链的事件传递方式

  如果事件当前对象(hit-test view或者第一相应者)不能处理事件的时候,UIKit框架将此事件传递给当前对象的nextResponder指向的对象。每一个响应者都将决定是否自己处理还是将事件继续传递给它通过nextResponder属性指向的想一个响应者直到有对象能够处理事件或者传递到链表结尾(application对象)。

通常事件当前对象是一个view。事件当前对象首先获得处理事件的机会。下面的图直观的解释了两种配置的事件传递方式。不同的配置会有不同的事件传递路径但是原理都是一样的。

对于左边的应用的事件传递路径如下:

  1. 事件当前view接受的事件或者方法。如果他不能够处理,他将事件传递给它的父view。

  2. 父view尝试去处理事件。如果父view还是不能处理此事件,他将事件传递给它的父view。
  3. 就这样直到将事件传递给controller的顶层view。如果顶层view还是不能处理该事件,顶层view会将事件传递给controller。
  4. controller尝试去处理事件,如果他也不能处理,则将事件传递给window。
  5. 如果window对象依然不能处理事件,则将事件转发给单例的application对象。
  6. 如果app对象不能处理事件,则将事件抛弃。

右边的应用的事件传递路径稍微有点不一样,如下:

  1. 在不能被处理的情况下事件将会被传递给当前controller的顶层view。

  2. 当前controller的顶层view不能处理就传递给他所在的controller。

  3. 当前controller将事件传递给他的顶层View的父view。

    步骤1-3将会不断的循环知道找到根controller。

  4. 根controller将事件传递给window对象。

  5. window对象将事件传递给app对象。

重要: 如果你自定义实现了一个view去处理事件,当发生你不关心的事件时候,不要在代码中直接去使用nextResponder属性直接调用响应者对象。你应该直接调动父类的事件处理方法,将事件参数直接传递给父类的事件处理方法。这样UIKit框架将自动会在响应者链中自动的去寻找事件的处理者。

6.思考

上面balabala说了一顿,纯粹是对于官方文档的翻译,但是即使读懂了官方文档依然会产生很多疑问。

疑问一:view在什么情况下能够变成第一响应者?条件是什么?

疑问二:如果一个touch事件发生后事件当前对象并不处理,他会正确传递给他的父view吗?要是此时认为设定firstResponder是其他view会出现什么结果呢?

上述纯粹是一个初学者的疑问,按照惯例我也将做一些测试区释疑这些问题。

 
7.证明
7.1为了证明上述问题写了一个小程序,它的机构如下:
7.2 其中View为XFFirstResponder实例,代码如下,其中根据第三节描述的要求,将canBecomeFirstResponder和becomeFirstResponder方法重写
#import "XFFirstResponder.h"

@implementation XFFirstResponder

-(BOOL) canBecomeFirstResponder
{
    return YES;
}// 打印按钮处理方法,用于测试定位第一响应者是谁
-(void)pressPrint
{
    NSLog(@"do pressPrint,I am in %@", self.name);
}

// 处理触摸事件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 打印当前对象处理结果
    NSLog(@"do touchBegan, I am in %@", self.name);

    // 继续执行父类方法,由此将得出整个响应者链路径
    [super touchesBegan:touches withEvent:event];
}

7.3 测试主方法设定如下:

@implementation ViewController

-(void)viewDidLoad
{
    // 将view设定名称便于区分
    self.viewA.name = @"viewA";
    self.viewB.name = @"viewB";
    self.viewC.name = @"viewC";
    self.viewD.name = @"viewD";

    // 将button设置action,target设置为nil,这将导致此事件去像第一相应者发送事件,便于我们定位第一响应者是谁
    [self.button addTarget:nil action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside];
}
 
疑问一:view在什么情况下能够变成第一响应者?条件是什么? 
只要继承自UIResponder的子类都可使以成为第一响应者的,但是必须在实现文件中覆盖canBecomeFirstResponder方法:
-(BOOL)canBecomeFirstResponder
{
    return YES;
}

然后在viewDidAppear方法中去调用想要成为第一响应者的becomeFirstResponder方法即可。

如在本例中将viewD设定我第一响应者

-(void) viewDidAppear:(BOOL)animated
{
    [self.viewD becomeFirstResponder];
}

然后去触发按钮事件:

    // 将button设置action,target设置为nil,这将导致此事件去像第一相应者发送事件,便于我们定位第一响应者是谁
    [self.button addTarget:nil action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside];

我们看到,由于按钮的target是设置为nil的,所以系统将去寻找当前第一响应者,如果第一响应者有pressPrint这个方法,那么他会调用它的pressPrint方法。而现在viewD中是实现了此方法:

// 打印按钮处理方法,用于测试定位第一响应者是谁
-(void)pressPrint
{
    NSLog(@"do pressPrint,I am in %@", self.name);
}

程序启动点击按钮,日志打印:

-- :::] do pressPrint,I am in viewD

说明,在例子中设定第一响应者成功。

疑问二:如果一个touch事件发生后事件当前对象并不处理,他会正确传递给他的父view吗?要是此时认为设定firstResponder是其他view会出现什么结果呢?

例如在本测试中,在view的实现方法中都实现了touchesBegan方法如下,他会将整触摸事件的响应者链打印出来。

// 处理触摸事件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 打印当前对象处理结果
    NSLog(@"do touchBegan, I am in %@", self.name);

    // 继续执行父类方法,由此将得出整个响应者链路径
    [super touchesBegan:touches withEvent:event];
}

情况1:

当当前对象有能力处理事件的时候:

此时我们去点击将viewD设定为第一响应者,然后去触摸viewC,那么这个触摸事件会第一时间找第一响应者吗?

日志打印为:
-- :::] do touchBegan, I am in viewC
-- :::] do touchBegan, I am in viewB
-- :::] do touchBegan, I am in viewA
可以看到在这个过程中虽然viewD已经是第一响应者,但是在日志打印看到并没有viewD出现。
这个可以总结为前文所述的:触摸事件的寻主是通过hit-testing的而不是响应者链,所以即使设定的第一响应者,对于touch事件来说根本不是去找第一响应者的。

情况二:

在情况一种,viewC是有能力处理触摸事件的,但是让他没有能力触发事件的时候回发生什么呢?会将事件转发给第一响应者吗?

1.创建NoToouchEventView类,依附于viewC,这个类是没有touch事件的。

 
2.启动程序,点击viewC
 
3.日志打印为:
-- :::] do touchBegan, I am in viewB
-- :::] do touchBegan, I am in viewA

可以看到,虽然viewD还是第一响应者,但是在touch事件中,依然按照hit-testing的机制在转发事件,并不理睬响应者链。

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

8.总结:
事件响应还是一个比较复杂的过程
按照事件类型可分为:
1.action事件,既按钮等设置的触发事件
2.用户事件,触摸,摇动等都属于用户事件
3.系统事件,闹钟等。
 
按照事件的寻主过程,可分为:
1.触摸事件使用的hit-testing过程
2.其他事件使用的响应链过程
 
 
这里有篇文章讲的很好,可以参考:
 
我会在深入理解touch事件和响应者链这篇文章中对其进行翻译。

[New learn]响应者链机制介绍的更多相关文章

  1. iOS响应者链和事件传递机制

    原文来自:http://www.cnblogs.com/zhw511006/p/3517248.html 响应者链(Responder Chain) 通常,一个iOS应用中,在一块屏幕上通常有很多的U ...

  2. 响应者链和Hit-Test 机制

    概念: 响应者 : 对用户交互动作事件进行响应的对象.响应者链:成为处理事件的响应者的先后顺序链. 1.Hit-Test 机制 当用户触摸(Touch)屏幕进行交互时,系统首先要找到响应者(Respo ...

  3. hit-testing机制介绍

    1.简介 寻找处理触摸事件的view的过程为hit-testing,找到的能够处理触摸事件的view叫做hit-test view. 2.机制介绍 假设下图为我们的手机屏幕,当我们假设点击了view ...

  4. Cocoa Touch事件处理流程--响应者链

    Cocoa Touch事件处理流程--响应者链 作者:wangzz 原文地址:http://blog.csdn.net/wzzvictory/article/details/9264335 转载请注明 ...

  5. iOS利用响应链机制点击tableview空白处关闭键盘-可以作为参考

    http://www.jianshu.com/p/9717b792599c   是原文地址 处理关闭键盘的做法一般分为两种:1.放弃第一响应者身份:2.当前视图结束编辑.通常情况下只要我们在合适的时机 ...

  6. Responder一点也不神秘————iOS用户响应者链完全剖析

    一.事件分类 对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕.晃动设备.通过遥控设施控制设备.对应的事件类型有以下三种: 1.触屏事件(Touch Event) 2.运动事件(Moti ...

  7. iOS Responder Chain 响应者链

    一.事件分类 对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕.晃动设备.通过遥控设施控制设备.对应的事件类型有以下三种: 1.触屏事件(Touch Event) 2.运动事件(Moti ...

  8. 响应者链UIResponder-扩大UIButton的点击范围

    在开发中,我们经常看到有按钮等的点击,会出现响应事件.按钮->view->ViewController->UIWindow->UIApplication,这就形成了一个响应链. ...

  9. AELF(ELF)区块链项目介绍

    AELF(ELF)区块链项目介绍,Aelf在交易所上的名称是ELF,最近涨了不少了,可以长期关注逢低建仓,根据自身情况可以适当轻仓配置点.AELF总结下来就是希望打造一个B2B的区块链开放式OS系统. ...

随机推荐

  1. c# 日志记录 行号

    Console.WriteLine(ex.Message); //通过如下代码来记录异常详细的信息 ); Console.WriteLine("文件名:{0},行号:{1},列号:{2}&q ...

  2. [洛谷P4688][Ynoi2016]掉进兔子洞

    题目大意:给定一个$n(n\leqslant10^5)$序列,$m(m\leqslant10^5)$个询问,每个询问给出$l_1,r_1,l_2,r_2,l_3,r_3$.令$s$为该三个区间的交集的 ...

  3. fhq_treap 学习笔记

    前言:昨天写NOIp2017队列,写+调辗转了3h+,不知道怎么的,就点进了一个神仙的链接,便在今日学习了神仙的fhq_treap. 简介:fhq_treap功能强大,支持splay支持的所有操作,代 ...

  4. JQuery仿淘宝滚动加载图片

    用 JQuery 制作随着显示页面的滚动条的滚动动态加载图片,适用于图片太多的页面,在访问网页时,可以先只加载第一屏要显示的图片,当用户进行向下滚动查看页面的时候,动态去加载这些图片,好处是减少页面第 ...

  5. angularJS 条件查询 品优购条件查询品牌(条件查询和列表展示公用方法解决思路 及 post请求混合参数提交方式)

    Brand.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...

  6. Linux IO Scheduler

    一直都对linux的io调度算法不理解,这段时间一直都在看这方面的内容,下面是总结和整理的网络上面的内容.生产上如何建议自己压一下.以实际为准. 每个块设备或者块设备的分区,都对应有自身的请求队列(r ...

  7. [HNOI2010] 弹飞绵羊 (分块)

    [HNOI2010] 弹飞绵羊 题目描述 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一条直线摆上 ...

  8. 题解【luogu1073 最优贸易】

    Solution 考虑原图是 DAG 时怎么做. 拓扑排序 + dp ,令 dp[i] 表示 \(1\) 到 \(i\) 的路径上最小的卖出价格.转移方程就是对每一个可以到达这个点的 dp 取个 mi ...

  9. Leetcode 002. 两数相加

    1.题目描述 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字. 如果,我们将这两个数相加起来,则会返回一个新的链表 ...

  10. FreeRTOS - 定时器使用注意

    1.只有进入定时器守护任务,从定时器命令队列取出命令,队列空间才会空出一个可用空间:所有定时器公用一个定时器队列 2.如果使用软件定时器,在调度器开始前,会自动创建一个定时器守护任务,configTI ...