[置顶] Responder一点也不神秘————iOS用户响应者链完全剖析
这篇文章想跟大家分享的主旨是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个方法:
-(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
{}
我们用的非常多,但是大家知道这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类的实例方法,它会返回任意对象在树中的上一个响应者实例:
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的下一个响应者是谁:
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>>
再继续往上追朔:
double delayInSeconds = 2.0;dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));dispatch_after(popTime, dispatch_get_main_queue(), ^(void){NSLog(@"%@",_testButton.nextResponder.nextResponder.nextResponder.nextResponder);});
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如下:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{NSLog(@"ViewController接收到触摸事件");
}
在appDelegate的中同样也写上这一段:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{NSLog(@"appDelegate接收到触摸事件");
}
那么究竟是谁被触发呢?
2013-09-21 04:02:49.405 响应链[743:60b] ViewController接收到触摸事件
这个很好理解,我刚刚也说了,viewController明显是appDelegate的子节点,他有事件分发的优先权。如果我们想两个地方都触发呢?这里super一下就可以了:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{[super touchesBegan:touches withEvent:event];NSLog(@"ViewController接收到触摸事件");
}
输出:
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上的事件分发。
转载于:https://www.cnblogs.com/james1207/p/3331169.html
[置顶] Responder一点也不神秘————iOS用户响应者链完全剖析相关推荐
- [置顶] 基于tlplayer的ios应用《虎跃在线课堂-英语篇》上线了
经过一次修改后,从新审核通过了.下载地址:https://itunes.apple.com/cn/app/id621202715?mt=8 此版本为免费应用,欢迎大家下载安装使用. 联系方式:wein ...
- 微信支持群消息置顶;博通洽购虚拟机巨头VMware;
微信支持群消息置顶 近日,微信iOS版更新了8.0.22版本,有用户发现,在该版本中,#微信新增群消息置顶功能#.群主或管理员长按需要置顶的消息,在菜单中点击置顶操作.之后消息就会置于群聊天界面的顶部 ...
- android备忘录_苹果备忘录怎样把内容置顶?有置顶功能的备忘录便签
苹果手机是很多人都比较喜欢的一个电子设备,人们之所以喜欢它,除了它精致的外观之外,还有更大一部分原因是因为它的性能非常强大,并且有很多实用的工具软件可以安装,备忘录就是其中一种.苹果备忘录怎样把内容置 ...
- 成都拓嘉启远:拼多多评论置顶该怎样去弄
有很多拼多多商家都是想要把好评置顶的,这样买家更容易看到店铺的优质好评,但是在拼多多上面其实是没有好评置顶的功能的,但是我们可以采取一些措施尽可能的让好评置顶,跟着小编一起来看看吧! 拼多多如何尽可能 ...
- html5置顶按钮如何添加,WordPress如何自定义文章开启置顶按钮?
WordPerss想制作置顶的功能,结果竟然发现自定义文章类型没有置顶的功能选项,查阅资料后发现WP只是没有显示置顶的选项,功能和文章类型一样.那么WordPress如何自定义文章开启置顶按钮? 添加 ...
- iOS动画一点也不神秘————你是喜欢看幻灯片?还是看高清电影?
iOS设备在平均线上硬件比andorid设备良好许多,尤其是内存和CPU,所以iOS应用里面有大量动画交互效果的交互,这是每个用户都喜悦的,如果每个操作对应界面来讲都是直接变化,那变得十分地生硬. 你 ...
- iOS之深入解析响应者链Responder Chain
一.响应链事件 iOS 中的事件可分为:触摸事件(multitouch events).加速计事件(accelerometer events).远程控制事件(remote control events ...
- [置顶] iOS中 支付宝钱包详解/第三方支付
[置顶] iOS中 支付宝钱包详解/第三方支付 韩俊强的博客 每日更新关注:http://weibo.com/hanjunqiang 新浪微博! 一.在app中成功完成支付宝支付的过程 1.申请支付 ...
- iOS UILabel 文字 置顶/置底 实现
iOS UILabel控件默认文字位置是居中的,如图所示: 但是我们经常碰到这样的需求,希望文字向上置顶,或者向下置底,但是很遗憾,iOS API中并没有提供相应的属性和方法,需要我们手动设置. 利用 ...
最新文章
- torch中tensor的普通索引以及index_select()
- SpringBoot项目的几种创建方式,启动、和访问
- clion opencv安装_Clion+Opencv3.2终极配置教程
- Linux高级编程--05.文件读写
- PERL模拟飞鸽传书文件传输总结
- Angular2学习笔记(六) Angular2 依赖注入
- javascript 以“年-月-日 时:分:秒”格式显示当前时间
- 简单了解oop编程思想和常见的几种设计模式
- 工程矩阵理论 Hermite 矩阵
- IT基础架构规划方案一(网络系统规划)
- 数据分析实例之股票市场数据分析
- 1. 神禹(shenyu)网关启动踩坑
- CommonAPI 使用说明文档
- Flutter报错:Bad state: Stream has already been listened to.
- 银行、支行、银行账号
- 【视频】谁说程序员无趣?这么多只有资深程序员才看懂的段子,会心一笑,深夜释放压力,随着视频哈哈哈哈哈笑出鹅声!
- Makefile原理
- Robomongo 的安装
- Web前端开发笔记——HTML和CSS
- 运用delphiXE RTTI在运行时动态获取信息及获取某个TComponent类或TObject类的RttiType信息的案例
热门文章
- JavaScript文档对象模型DOM节点操作之第一个子元素和最后一个子元素(3)
- java线上诊断工具,Java线上诊断神器Arthas-1
- router优点 vue_Vue 出场率99%的面试题
- lua游戏脚本实例源码_Redis Lua脚本中学教程(上)
- 学Python必看,Python中encode(),decode()的本质
- 计算机组成原理—读写周期与半导体只读存储器
- 蛮力法 —— 求解迷宫问题 —— DFS和BFS
- 出现(n+1)/2次的数 — 给n个数字,求至少出现(N+1)/2次的那个数字?
- [Python] 生成迭代器 iter() 函数
- jQuery Mobile中尾部栏footer的data-*选项