这篇文章想跟大家分享的主旨是iOS捕获用户事件的各种情况,以及内部封装的一些特殊事件。

我们先从UIButton谈起,UIButton大家使用的太多了,他特殊的地方就在于其内置的普通Default/高亮Highlighted/选择Selected/可用Enable的几个状态(UIControlState)。其次就是SDK内部已经为我们封装了以下用户事件:

最常用的莫过于Touch Up Inside这个事件了,他代表:  用户在按钮区域内按下,并且也在按钮区域内松开。

关键点:按下并且松开 才能触发此方法,也就是正确的操作 按下一次,松开一次只会触发一次此事件。与之不同的Touch Drag Inside等方法不需要松开这个过程,Up变为了Drag,其实大家都能理解,SDK在封装的时候原理跟UITouchEvent是一个道理,第一个单词Touch 代表按下(Began)第二个单词Up代表松开(Ended),Drag代表拖动(Moved)。TouchMoved方法在一次完整的触摸中会被触发很多次,所以Touch Drag Inside方法会在用户手松开之前一直被触发。

这些就是UIButton已封装的事件,而UIButton继承自UIControl。UIControl又继承自UIView。我们平时能用这些已封装的事件的控件都是UIControl的子类。那么父类UIView是没有内部事件的。

我们常常利用UIView来写自己的UITouchEvent。例如在一个View/ViewController中直接实现以下3个方法:

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  2. {
  3. }
  4. -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
  5. {
  6. }
  7. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
  8. {
  9. }
  10. -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
  11. {
  12. }

我们用的非常多,但是大家知道这4个方法是谁的实例方法吗?如果你一下就说出是UIView的,那么为什么我们在UIViewController中也可以用呢,他们不是继承关系。

注意这4个实例方法来自UIView与UIViewController的共同父类:UIResponder。它是我们今天的主角。

基本上我们所能看到的所有图形界面都是继承自UIResponder的,So,它究竟为何方神圣?

UIResponder所谓很多视图的父类,他掌管着用户的操作事件分发大权。如果没有他,我们的电容屏如何将用户的操作传递给我们的视图令其做出反应呢?

我们先看看iOS中的响应者链的概念:

每一个应用有一个响应者链,我们的视图结构是一个N叉树(一个视图可以有多个子视图,一个子视图同一时刻只有一个父视图),而每一个继承UIResponder的对象都可以在这个N叉树中扮演一个节点。当叶节点成为最高响应者的时候,从这个叶节点开始往其父节点开始追朔出一条链,那么对于这一个叶节点来讲,这一条链就是当前的响应者链。响应者链将系统捕获到的UIEvent与UITouch从叶节点开始层层向下分发,期间可以选择停止分发,也可以选择继续向下分发。

例子:

我用SingleView模板创建了一个新的工程,它的主Window上只有一个UIViewController,其View之上有一个Button。这个项目中所有UIResponder的子类所构成的N叉树为这样的结构:

那么他看起来并不像N叉树,但是不代表者不是一颗N叉树,当我们项目复杂之后,这个View可不可以有多个UIButton节点?所以他就是一棵树。

实际上我们要把这棵树写完整,应该还要算上UIButton的UILabel和UIImageView,因为他们也是UIReponder的子类。这里先不考虑了。

我们对UIButton来讲,他此时若是叶节点,那么这时我们针对他所在的响应链来说,他在他之前的响应者就应该是我们controller的view(树中的叶节点比父节点永远更优先被分发事件,但是并不是说他就能在时间上先响应,我们下面讲为什么)。所以我们尝试在任意地方打印这个Button的nextReponder对象。nextResponder对象是UIReponder类的实例方法,它会返回任意对象在树中的上一个响应者实例:

  1. NSLog(@"%@",_testButton.nextResponder);

控制台输出消息:

2013-09-21 03:40:25.989 响应链[614:60b] <UIView: 0x16555e10; frame = (0 0; 320 568); autoresize = RM+BM; layer = <CALayer: 0x16555e70>>

我们可以根据这个UIView的尺寸来得知,他就是我们唯一的控制器中的那个UIView。

接下来我们再打印下这个UIView的下一个响应者是谁:

  1. NSLog(@"%@",_testButton.nextResponder.nextResponder);

输出:

2013-09-21 03:45:03.914响应链[621:60b] <RSViewController: 0x15da0e30>

依次看,接着加一个nextResponder:

2013-09-21 03:50:49.428 响应链[669:60b] (null)

为什么这里ViewController没有父亲呢?

注意这句代码我是写在ViewDidLoad中,而我们知道这个方法的生命周期比较早,所以我们换个地方写或者延迟一段时间再打印,两种方法都可以得到结果(由此可以推理出我们响应者树的构造过程是在ViewDidLoad周期中来完成的,这个函数会将当前实例的构成的响应者子树合并到我们整个根树中):

2013-09-21 03:53:47.304 响应链[681:60b] <UIWindow: 0x14e24200; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14e242e0>; layer = <UIWindowLayer: 0x14e244a0>>

再继续往上追朔:

  1. double delayInSeconds = 2.0;
  2. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
  3. dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  4. NSLog(@"%@",_testButton.nextResponder.nextResponder.nextResponder.nextResponder);
  5. });

2013-09-21 03:56:22.043 响应链[690:60b] <UIApplication: 0x15659c00>

再加一个:

2013-09-21 03:56:51.186 响应链[696:60b] <RSAppDelegate: 0x16663520>

那么我们的appDelegate还有没有父节点?

2013-09-21 03:57:22.588 响应链[706:60b] (null)

没有了,注意,一个从叶节点开始分发的事件,最多也就只能分发到我们的AppDelegate了!

这个树形结构在我们的项目中尤为重要,举个栗子,如果我们想在一个view中重写UITouchEvent的4个方法,并且不影响他的父视图也响应这些事件,就要注意你重写的方式了,比如我们在ViewController中重写touchBegan如下:

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  2. {
  3. NSLog(@"ViewController接收到触摸事件");
  4. }

在appDelegate的中同样也写上这一段:

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  2. {
  3. NSLog(@"appDelegate接收到触摸事件");
  4. }

那么究竟是谁被触发呢?

2013-09-21 04:02:49.405 响应链[743:60b] ViewController接收到触摸事件

这个很好理解,我刚刚也说了,viewController明显是appDelegate的子节点,他有事件分发的优先权。如果我们想两个地方都触发呢?这里super一下就可以了:

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  2. {
  3. [super touchesBegan:touches withEvent:event];
  4. NSLog(@"ViewController接收到触摸事件");
  5. }

输出:

2013-09-21 04:07:26.206 响应链[749:60b] appDelegate接收到触摸事件

2013-09-21 04:07:26.208 响应链[749:60b] ViewController接收到触摸事件

注意看时间戳,appDelegate虽然优先级别不如ViewController,但是他响应的时间上面足足比ViewController早了0.002秒,我这里试了几次,都是相差0.002秒。

那么我们分析一下这里的响应者链是怎样工作的:

用户手指触摸到了UIView上,由于我们没有重写UIView的UITouchEvent,所以他里面和super执行的一样的,将该事件继续分发到UIViewController;

UIViewController的TouchBegan被我们重写了,如果我们不super,那么我们在这里写响应代码。事件到这里就不继续分发了。可想而知,UIViewController祖先节点:UIWindow,UIApplication,AppDelegate都无权被分发此事件。

如果我们super了TouchBegan,那么此次触摸事件由

ViewController分发给UIWindow,

UIWindow继而分发给UIApplication,

UIApplication再分发给AppDelegate,

于是我们在ViewController和appDelegate的touchBegan方法中都捕获到了这次事件。

到这里大家应该对这个响应者树有一个很好的理解了吧?

接下来我们再谈谈第一响应者,和UIButton上的事件分发。

//转自:http://blog.csdn.net/mobanchengshuang/article/details/11858217

/* 这样可以获取当前的第一响应者

UIWindow *keyWindow = [[UIApplicationsharedApplication] keyWindow];

UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];

*/

iOS用户响应者链的那些事儿的更多相关文章

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

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

  2. [置顶] Responder一点也不神秘————iOS用户响应者链完全剖析

    这篇文章想跟大家分享的主旨是iOS捕获用户事件的各种情况,以及内部封装的一些特殊事件. 我们先从UIButton谈起,UIButton大家使用的太多了,他特殊的地方就在于其内置的普通Default/高 ...

  3. iOS 事件响应者链的学习(也有叫 UI连锁链)

    当发生事件响应的时候,必须知道由谁来响应事件.在iOS中,由响应链来对事件进行响应,所有的事件响应的类都是继承于UIResponder的子类,响应链是一个由不同对象组成的层次结构,其中每个对象将依次获 ...

  4. [New learn]响应者链机制介绍

    1.简介  测试代码库:https://github.com/xufeng79x/EventHandler 响应者链是系统寻找事件相应者的一个路径,他是同touch事件的Hit-testing过程具有 ...

  5. iOS Responder Chain 响应者链

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

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

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

  7. IOS开发中响应者链

    在IOS开发中,有时候会遇到如下情况:在页面1上有一个RedView,在RedView上有一个GreenView,在GreenView上有一个button,这些view的创建代码如下: 1.AppDe ...

  8. iOS事件传递和事件响应者链 20170810

    一.事件响应者链 事件传递和事件响应链 区别 事件的传递和响应的区别: 事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件. 引出 当我们手指触摸屏幕 ...

  9. iOS 之 事件响应者链

    响应者链表示一系列的响应者对象.事件被交由第一个响应者对象处理,如果第一个响应者不处理,事件就沿着响应者链向上传递,交由下一个响应者(Next responder). View->ViewCon ...

随机推荐

  1. iOS 实现设备应用之间的相互切换

    今天突发奇想,想做个应用之间能够切换的demo. 前提条件是,你得知道对应应用程序的URL Scheme.URL Scheme不一定和bundle id相同,但是,一定要是一致的. [[UIAppli ...

  2. java 两个csv文件数据去重

    1.pom.xml配置 <dependency> <groupId>commons-io</groupId> <artifactId>commons-i ...

  3. sql server获取后天距离某一日期还有多少周的写法

    ),,),'2012-10-18 00:00:00.000')

  4. IPV4的地址是如何分类的?网络号的范围分别是多少?

    1. A类地址 (1)A类地址第1字节为网络地址,其它3个字节为主机地址. (2)A类地址范围:1.0.0.1—126.255.255.254 (3)A类地址中的私有地址和保留地址: ① 10.X.X ...

  5. role management

    role management https://panjiachen.github.io/vue-element-admin/#/permission/page

  6. POJ 1523 SPF 求割点的好(板子)题!

    题意: 给个无向图,问有多少个割点,对于每个割点求删除这个点之后会产生多少新的点双联通分量 题还是很果的 怎么求割点请参考tarjan无向图 关于能产生几个新的双联通分量,对于每个节点u来说,我们判断 ...

  7. BZOJ5154 [Tjoi2014]匹配 【KM算法 + 枚举】

    题目链接 BZOJ5154 题解 先跑出一个匹配方案 然后暴力删去每对匹配再检验一下答案是否减小 使用KM算法提升速度 #include<algorithm> #include<io ...

  8. latex公式测试

    https://katex.org/docs/supported.html 1.x_n $x_n$ 2 . \sum $\sum$ \sum_{i=1}^n $\sum_{i=1}^n $ 3.\bi ...

  9. windows7下检测耳机麦克拔插(转)

    原文转自 https://blog.csdn.net/rankun1/article/details/50972990 #include "stdafx.h" #define SA ...

  10. python任意编码转utf8或者unicode

    # encoding: utf-8 ''' Created on 2015年2月8日 @author: 张鹏程 aprial@163.com @copyright: 版权所有, 尊重劳动成功, 转载与 ...