一. 声明: 

  本文意在探讨, 也参考了几位大神的文章, 在最后我会把链接发出来, 如果有理解错误的地方, 请大神们指正哈!

二. 前言: 

  最近自己做项目的时候, 用到了UITabbarController的UITabbar, 我们的设计是这样的, 如图:

很常见的一种标签选择, 一个功能模块: 首页, 本地, 扫一扫, 入驻, 个人中心. 但是中间的扫一扫功能上半部分点击没有响应, 只有在UITabbar上的下半部分扫一扫点击才有响应, 那么我们如何来解决它呢? 下面就是今天咱们要探讨的内容: 事件传递和响应者链.

三. 正文: 

1. 概念(知识点):

首先, 咱们在开始探讨之前, 先储备下几个概念或者说是知识点:

(1). 响应者对象: 可以响应事件的对象, 就是响应者对象. 继承自UIResponder的对象, 才可以响应事件. UIWindow, UIViewController, UIView, UIButton都是继承自UIResponder对象, 所以都可以响应事件

(2). 上一响应者: 视图的nextResponder属性, 得到的对象就是当前视图的上一响应者. 原则: 判断当前View是否是控制器的View (a). 如果是, 则上一响应者就是控制器 (b). 如果不是, 则上一响应者就是父控件

(3). 触摸对象: 一根手指触摸屏幕时就会产生一个UITouch对象, 也就是触摸对象, 所以我们可以根据UITouch对象的数量, 来判断用户有几根手指在操作屏幕

(4). 事件对象: 手指触摸屏幕时也会随之产生一个UIEvent对象, 也就是事件对象

2. 事件的生命周期

一个事件的生命周期: 从下往上找合适的视图, 从上往下找可响应的视图, 如果找不到则废弃该事件, 如图:

从图中我们可以看到: 左边为事件的产生和传递的过程, 右边为事件的响应过程(响应者链)

首先是事件传递过程: 从下到上 从父控件到子控件

AppDelegate -> UIWindow -> UIViewController -> UIView

事件传递原则: 1. 自己是否能接收事件; 2. 触摸点是否在自己身上.

通过以下两个方法, 来实现上面两个原则的判断:

(a). hitTest:withEvent: 返回合适的视图

(b). pointInside:withEvent: 判断点是否在自己身上
当一个触摸事件发生后, UIApplication会将事件加入到一个事件队列中, 因为队列遵从FIFO原则(先进先出), 先发生的事件, 先处理.

(a). UIApplication从队列中取出一个事件后, 将该事件传递给UIWindow, UIWindow检查自己是否符合事件传递原则

(b). 如果UIWindow符合, 则将该事件传递给UIViewController, UIViewController检查自己是否符合事件传递原则

(c). 如果UIViewContrller符合, 则将该事件传递给UIView, UIView检查自己是否符合事件传递原则

(d). 如果UIView符合, 则继续传递给自己的子控件, 如果没有子控件, 则事件传递过程结束. UIView就是合适的视图.

其次是事件响应过程: 从上到下 从子控件到父控件

重写touchesBegan:withEvent:方法就可以响应事件

UIView -> UIViewController -> UIWindow -> AppDelegate

(a). 如果UIView是合适的视图, 如果UIView不能响应事件, 则找自己的下一响应者, 也就是自己的父控件, 自己的父控件是UIViewController的View

(b). 如果View不能响应事件, 则找自己的下一响应者, 因为View是控制器的View, 所以上一响应者是UIViewController

(c). 如果UIViewController不能响应事件, 则找自己的下一响应者: UIWindow

(d). 如果UIWindow不能响应事件, 则找自己的下一响应者: AppDelegate

(e). 如果AppDelegate不能响应事件, 则事件忽略, 废弃

3. 注意事项: 

(1). 视图不能接收事件的三种情况: (a). 当不允许用户交互时, 也就是视图的userInterActionEnabled属性值为NO时 (b). 当视图隐藏时, 也就是视图的hidden属性值为YES时 (c). 当视图的透明度在0.00 - 0.01范围时, 也就是视图的alpha属性值在0.00 - 0.01范围时
(2). 父控件对子控件的三种影响: (a). 当父控件不能接收事件时, 那么它所有的子控件也不能接收触摸事件 (b). 父控件的hidden会直接影响子控件的hiden (c). 父控件的alpha会直接影响子控件的hidden

(3). 事件传递如何寻找最合适的视图: (a). 判断自己是否能接收触摸事件 (b). 判断触摸点在不在自己身上 (c). 如果以上a, b两种情况都符合, 就从后往前遍历自己的子控件, 然后再重复a, b两个步骤 (d). 如果没有符合条件的子控件, 那么自己就是最合适的视图

四. Talk is cheap. Show me the code.

代码最终呈现出来的结果是这样的, 代码的目录结构是这样的,

应用的根视图是ViewController, 自定义的红, 橙, 黄, 绿, 青五个视图都添加到ViewController的View上

ViewController里的代码是这样的:

    // 第一层ViewLCLRedView * redView = [LCLRedView new];redView.frame = CGRectMake(20, 64 + 20, 300, 150);[self.view addSubview:redView];// 第一层ViewLCLOrangeView * orangeView = [LCLOrangeView new];orangeView.frame = CGRectMake(20, CGRectGetMaxY(redView.frame) + 20, 300, 300);[self.view addSubview:orangeView];// 第二层ViewLCLYellowView * yellowView = [LCLYellowView new];yellowView.frame = CGRectMake(0, 0, 200, 200);[orangeView addSubview:yellowView];// 第二层ViewLCLGreenView * greenView = [LCLGreenView new];greenView.frame = CGRectMake(150, 150, 150, 150);[orangeView addSubview:greenView];// 第三层ViewLCLCyanView * cyanView = [LCLCyanView new];cyanView.frame = CGRectMake(0, 0, 100, 100);[yellowView addSubview:cyanView];

自定义View里的代码是这样的, 下面是红色View里的代码, 其它自定义View的代码只是更改了对应的背景色

- (instancetype)init {if (self = [super init]) {self.backgroundColor = [UIColor redColor];}return self;
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {NSLog(@"%s", __func__);
}

代码就是这些, 其实关于事件的传递和响应, 代码不是很多, 重要的在思想上的理解, 接下来我们要做得就是点点这几个视图, 大家就可以理解啦!

好, 下面大家来跟着我做做练习

1. 点击红色视图, 控制台打印的是什么?

2. 点击橙色视图, 控制台打印的是什么?

3. 点击黄色视图, 控制台打印的是什么?

4 点击绿色视图, 控制台打印的是什么?

5. 点击青色视图, 控制台打印的是什么?

6. 当我们把黄色视图的userInterActionEnabled属性设置为NO时, 点击青色视图, 打印的是什么?

下面我们来分析第6种情况:

首先两个原则: (a). 自己是否能接收触摸事件 (b). 触摸点是否在自己身上

(1). 当我们点击青色视图, 事件产生, UIApplication开始处理这个事件, UIApplication将这个事件传递给UIWindow, 也就是keyWindow

(2). keyWindow接收事件, 检查自己能接收触摸事件, 触摸点也在自己身上, keyWindow将这个事件传递给ViewControler

(3). ViewController接收事件, 检查自己能接收事件, 触摸点也在自己身上, ViewControler的View从后往前遍历自己的子控件

(4). ViewController的View有两个子控件, 红色视图和橙色视图, 橙色视图为后添加的, 所以将这个事件传递给橙色视图,

(5). 橙色视图接收事件, 检查自己能接收事件, 触摸点也在自己身上, 橙色视图从后往前遍历自己的子控件

(6). 橙色视图有两个子控件, 黄色视图和绿色视图, 绿色视图为后添加的, 所以将事件传递给绿色视图

(7). 绿色视图接收事件, 检查自己能接收事件, 但是触摸点不在自己身上. 所以橙色视图又将事件传递给黄色视图

(8). 黄色视图接收事件, 检查自己不能接收事件. 所以橙色视图继续遍历自己的子控件

(9). 橙色视图遍历完自己的子控件, 发现没有合适的视图, 所以橙色视图就是最合适的视图, 事件传递过程停止.

其次, 找到最合适的视图后, 开始事件响应过程

(1). 橙色视图检查自己是否能响应事件, 如果能, 处理事件, 整个事件结束. 如果不能, 将事件给下一响应者处理.

(2). 因为我们在橙色视图里重写了touchesBegan:withEvent:方法, 所以橙色视图能响应事件, 事件结束.

到此, 练习结束, 如果分析得有错误的地方, 或者大家有不同的见解, 欢迎大家过来讨论! 共同进步!

回首开篇: 我们提到的UITabbar突出按钮的处理:

1. 首先我们要自定义一个UITabBar类

下面是我们的自定义UITabBar类LCLTabBar, 重写它的hitTest:withEvent:方法, 作用是: 返回可处理该触摸事件的视图

#import <UIKit/UIKit.h>@interface LCLTabBar : UITabBar/** 中间的凸起按钮 */
@property (nonatomic, strong) UIButton * centerButton;@end

#import "LCLTabBar.h"@implementation LCLTabBar- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {/**self.centerButton: UITabBar中间的凸起按钮point: 用户的触摸点判断用户的触摸点是否在UITabBar中间的凸起按钮上如果是, 让UITabBar中间的凸起按钮响应事件如果不是, 让UITabbar响应事件*/BOOL isContains = CGRectContainsPoint(self.centerButton.frame, point);if (isContains) {return self.centerButton;} else {return [super hitTest:point withEvent:event];}
}@end

2. 然后我们在根视图里面, 根视图是继承自UITabBarController的子类, 这样写:

    /** 将系统的tabBar换成我们自定义的tabBar */LCLTabBar * tabBar = [LCLTabBar new];[self setValue:tabBar forKey:@"tabBar"];/** 中间的按钮 */CGFloat centerButtonWidth = 50; // 中间按钮的大小UIButton * centerButton = [UIButton buttonWithType:UIButtonTypeCustom];[centerButton setBackgroundColor:[UIColor whiteColor]];[centerButton setImage:[UIImage imageNamed:@"图片名字"] forState:UIControlStateNormal];centerButton.frame = CGRectMake((self.view.frame.size.width- centerButtonWidth) / 2, -centerButtonWidth/2 + 5, centerButtonWidth, centerButtonWidth);centerButton.layer.cornerRadius = centerButtonWidth/2; // 圆形按钮centerButton.layer.masksToBounds = YES;[tabBar addSubview:centerButton];tabBar.centerButton = centerButton;[centerButton addTarget:self action:@selector(centerButtonAction) forControlEvents:UIControlEventTouchUpInside];

完成以上两步, 我们的问题, 也就解决啦!

五. 参考文章

1. iOS事件拦截和事件转发

2. iOS事件传递响应机制

至此为止, 我们的<iOS底层原理探究>第一探也就结束啦, 如果本文有描述不正确的地方, 或者大家有不同的意见, 欢迎来此讨论!

Talk is cheap. Show me the code.

转载于:https://www.cnblogs.com/ZeroHour/p/9460611.html

iOS底层原理探究 第一探. 事件传递和响应者链相关推荐

  1. iOS底层原理探究-Runloop

    Runloop 1. 概述 一般来说,一个线程只能执行一个任务,执行完就会退出,如果我们需要一种机制,让线程能随时处理时间但并不退出,那么 RunLoop 就是这样的一个机制.Runloop是事件接收 ...

  2. iOS底层原理之架构设计

    文章目录 何为架构? MVC - Apple版 MVC – 变种 MVP MVVM 设计模式 面试题 何为架构? 架构(Architecture):软件开发中的设计方案,类与类之间的关系.模块与模块之 ...

  3. iOS底层原理之内存管理

    文章目录 定时器 CADisplayLink.NSTimer GCD定时器 内存管理 iOS程序的内存布局 Tagged Pointer OC对象的内存管理 拷贝 引用计数的存储 dealloc 自动 ...

  4. KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听

    书读百变,其义自见! 将KVO形式以代码实现呈现,通俗易懂,更容易掌握 :GitHub   -链接如果失效请自动搜索:https://github.com/henusjj/KVO_base 代码中有详 ...

  5. 视频教程-iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-iOS

    iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化 小码哥教育CEO,曾开发了2个iOS的流行开源框架(MJRefresh.MJExtension),目前在国内的使用率非常高. 李 ...

  6. iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-李明杰-专题视频课程...

    iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-236人已学习 课程介绍         得遇名师,突飞猛进!iOS培训王者MJ(李明杰)老师精心研发,iOS进阶课程,实用技术 ...

  7. iOS底层原理 - 常驻线程

    iOS底层原理 - 常驻线程 在 AFN 2.0 时代,会经常看到 AFN 创建一个常驻线程的方式: 0️⃣ AFN 2.0 时代的常驻线程 + (NSThread *)networkRequestT ...

  8. View工作原理(一)事件传递原理详解

    版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 转载请说明出处:http://blog.csdn.net/ff20081528/article/details/17353869 ...

  9. iOS之深入解析事件传递的响应链

    一.UIResponder App 使用响应者对象接收和处理事件,只有继承 UIResponder 的类,才能处理事件. UIApplication.UIView.UIViewController 都 ...

最新文章

  1. Spring Cloud第十三篇: 断路器聚合监控(Hystrix Turbine)
  2. 作为导师,我希望学生在毕业后主动拉黑我
  3. 今晚直播 | NeurIPS 2021论文解读:基于置信度校正的可信图神经网络
  4. ASP.NET中UpdatePanel与jQuery同时使用所遇问题解决
  5. 使用oss对象存储的ossutil工具
  6. 天堂2单机版进去显示服务器维护,天堂2五章单机版设置完成后却玩不了 高手解决一下...
  7. 1024程序员节:心疼被段子手黑得最惨的他们
  8. python浪漫代码表白npy_python实现npy格式文件转换为txt文件操作
  9. 360度全景视频html,360度全景视频是怎么拍摄出来的?
  10. GitHub:git push问题remote:Support for password authentication was removed on August 13,2021.
  11. activity串行多实例审批
  12. Visitor模式实践
  13. 配置文件,运行级别,软件安装方式,运维职责
  14. 用css、js实现字幕横向滚动
  15. Python3,我用这种方式讲解python模块,80岁的奶奶都说能理解。建议收藏 ~ ~
  16. STM32-输出比较
  17. 技术派-减少connect超时设置
  18. python 凯利公式_蒙特卡罗方法验证凯利公式
  19. chrome浏览器登录谷歌账号显示“为安全起见,您无法从此设备登录。请稍后重试,或从其他设备登录”
  20. Julia学习笔记:堆积柱形图的绘制

热门文章

  1. 符号函数(sign function)性质及应用
  2. 电子计算机制作探测,金属探测器DIY,你真正了解过多少?
  3. 四旋翼无人机学习第2节--cadence工程创建与原理图的添加
  4. windoes 平台 Qt 的下载与安装-(Qt 5.15.2 LTS,这是一个长期支持版本)
  5. 最新法定假日修改及2008年法定假日安排
  6. 中国计算机设计大赛来啦!用飞桨驱动智慧救援机器狗
  7. 海盗云商php,海盗云商(2.2.1.160429)前台无限制注入(无需登录,无视GPC)
  8. UHD-SDI GT v2.0(PG380)
  9. 凸函数高维性质证明(Jenson不等式)
  10. Kotlin (Java) 获取 mysql 数据库的所有表,表的所有字段,注释,字段类型