• 概述

在iOS中,视图的层级一般都是 父视图->添加各种子视图。这时候某个视图(子视图)上有个按钮,需要我们交互。但是有时候我们会发现无论如何都没有反应。这时候可能就是我们对iOS的事件传递响应还有些迷茫。

  1. 事件的传递:简单的来说就是事件的传递顺序。他是系统向可响应的离用户最近的视图传递。大致流程就是 UIKit -> ... -> root view -> ... -> initial view 。(方式是从上到下传递)
  2. 事件的响应:在我们的视图中一般都是树状结构,有层级关系。那么这时候用户点击某个控件,所触发的是子视图还是父视图,这种有一个先后的关系,就构成了一个链条,我们就叫做“响应者链条”。响应的大致顺序就是,首先查看initial view 是否能够处理这个事件,如果不能事件上传给其父视图;如若上级视图仍然不能够处理则会继续上传;一直传递到视图的控制器,那么首先判断该控制器的根视图View是否能够处理此事件;如果不能那么继续上传(对于目前本身的视图控制器本身还在另一个视图控制器中,则继续交由给其父控制器的根视图继续处理,如果不能那么就要交给父控制器的控制器来处理);一直到 window,如果还是不能处理,那么就要交给 application 处理,还是不能那么就被丢弃。(传递方式是从下到上传递)
  • iOS中的事件
  1. 触摸事件
  2. 加速计事件
  3. 远程控制事件
  • 触摸事件

  响应者对象(UIResponder)

  在iOS中,只要是继承UIResponder的对象都可以接收并处理事件。在iOS中提供了一些方法来处理触摸事件。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;      // 开始触摸View时会调用一次
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;      // 随着手指一动会多次调用
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;      // 手指离开的时候会调用
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;    // 触摸结束前,电话打进来,会自动调用这个方法

  事件的产生

  当发生一个触摸事件后,系统会将触摸事件添加到UIApplication管理的事件队列中(先进先出) ->  UIApplication 从事件队列中拿出最前的事件将之分发出去,通常是首先发送事件给应用程序的主窗口  ->  主窗口会找到一个最合适的视图来处理触摸事件  ->  找到合适的视图控件后,就会调用控件的上述方法中的一个或者多个来处理具体的事件处理。

  事件的传递

  主窗口先判断能不能接收这个触摸事件,如若不能,就直接return;

  主窗口可以接收,传递给子视图,继续判断,继续传递,循环直到没有能够符合响应的子控件,那么这时候的就会认为由自己来处理这个事件最合适。

  也有不能响应的情况:

    1.  不允许交互

    2.  控件隐藏

    3.  透明度过低(<0.01)

  如何寻找最适合的控件来处理事件

  UIView 及其子类有两个非常重要的方法

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds

  当只要有事件传递给这个控件,这个控件就会调用

hitTest: withEvent:

  其作用是寻找并返回最合适的View,不管这个控件能不能处理事件,也不管触摸点是不是在这个空间上,都会先接收事件,然后调用方法。

  所以这里我们就有了可操作空间 , 因为不管点击事件发生在哪里,最终能够处理事件的View都是这个方法返回的View。通过重写这个方法我们可以拦截整个事件的传递过程,同时可以指定处理事件的View。(如果这个方法返回的是nil,那么调用该方法的控件本身以及其子控件均不能处理事件,只能由其父视图来处理事件)

  所以事件的传递顺序 :产生触摸事件 -> UIApplication事件队列 -> [UIWindow hitTest:withEvent:] -> 返回更合适的View -> [子控件 hitTest:withEvent:] -> 返回最合适的View ...

  所以这里我们可以得到的结论就是:不管子控件是不是最合适的View,都会调用 hitTest 方法,如果不是最合适的View,会返回nil,同时认定其父视图是最合适的View。

  小技巧:在父控件中返回最合适的子控件。因为如果在自己返回自己,有可能两个视图 B,C 同时加载 A 上,当设置B为最合适的View,这时候如果我们在 B 中返回自己,可能我们点击到 C 这时候 B 还没来及返回系统就已经定位到了 C 。

  寻找最合适的View底层剖析

// 什么时候调用:只要事件一传递给一个控件,那么这个控件就会调用自己的这个方法
// 作用:寻找并返回最合适的view
// UIApplication -> [UIWindow hitTest:withEvent:]寻找最合适的view告诉系统
// point:当前手指触摸的点
// point:是方法调用者坐标系上的点
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 1.判断下窗口能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2.判断下点在不在窗口上
// 不在窗口上
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历子控件数组
int count = (int)self.subviews.count;
for (int i = count - ; i >= ; i--) {
// 获取子控件
UIView *childView = self.subviews[i];
// 坐标系的转换,把窗口上的点转换为子控件上的点
// 把自己控件上的点转换成子控件上的点
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) {
// 如果能找到最合适的view
return fitView;
}
}
// 4.没有找到更合适的view,也就是没有比自己更合适的view
return self;
}

  通过重写 View 的 hitTest 方法,即可找到最合适的 View

  另一个比较重要的方法

pointInside: withEvent: 

  方法是用来判断我们触摸事件的点位置是否在当前View上,如果返回 NO 说明是不在当前 View 坐标系上,同时自然是不能够处理事件的。

  事件的响应

  传递方式是 从下往上 的传递方式。

  

  事件处理流程

  产生触摸事件 -> 事件添加到 UIApplication 队列中 -> 事件传递主窗口 -> 找到最合适的View -> 最合适的View调用自己的touch方法来处理事件 -> touches默认做法是把事件顺着响应链往上传递

//只要点击控件,就会调用touchBegin,如果没有重写这个方法,自己处理不了触摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 默认会把事件传递给上一个响应者,上一个响应者是父控件,交给父控件处理
[super touchesBegan:touches withEvent:event];
// 注意不是调用父控件的touches方法,而是调用父类的touches方法
// super是父类 superview是父控件
}

  当我们需要做到一个事件多个对象同时处理的话,我们就可以先处理自己的事件之后,调用 super 方法。

  • 实例应用

  当我们要扩大按钮点击范围

  比如我们有一个 20pt*20pt 的 按钮,我们可以在一个控件的中利用 hitTest 来实现。 例如一个 UIButton,自定义一个按钮,在其自定义类中重写方法。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判断下窗口能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 扩大到按钮之外的都是点击范围
CGRect touchRect = CGRectInset(self.bounds, -, -);
if (CGRectContainsPoint(touchRect, point)) {
for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subView convertPoint:point toView:self];
UIView *hitTestView = [subView hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}

  将事件传递给兄弟View(A 与 B是同一个父视图,但是 B 有部分遮挡住了 A ;点击遮挡部分需要 A 响应事件)这时候点击 A 是不会有任何响应的,除非 B 的userInteractionEnable 为 NO , 但是我们用 hitTest 同样可以做到,重写 B 的这个方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitTestView = [super hitTest:point withEvent:event];
if (hitTestView == self) {
hitTestView = nil;
}
return hitTestView;
}

  

  

iOS事件响应链(Responder Chain)的更多相关文章

  1. iOS事件响应链

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

  2. iOS中的事件响应链、单例模式、工厂模式、观察者模式

    学习内容 欢迎关注我的iOS学习总结--每天学一点iOS:https://github.com/practiceqian/one-day-one-iOS-summary iOS中事件传递和相应机制 i ...

  3. 追踪app崩溃率、事件响应链、Run Loop、线程和进程、数据表的优化、动画库、Restful架构、SDWebImage的原理

    1.如何追踪app崩溃率,如何解决线上闪退 当 iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上.crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈 跟踪信 ...

  4. iOS-UIResponse之事件响应链及其事件传递

    UIResponse之事件响应链及其事件传递 我们的App与用户进行交互,基本上是依赖于各种各样的事件.一个视图是一个事件响应者,可以处理点击等事件,而这些事件就是在UIResponder类中定义的. ...

  5. Objective-C 事件响应链

    苹果app使用响应者对象(responder object)来接收和处理事件.响应者对象是NSResponder及其子类的实例,如NSView.NSViewController和NSApplicati ...

  6. 【Demo 0010】事件响应链

    本章学习要点:       1.  熟悉iOS事件分发过程以及事件响应链;       2.  掌握基本事件响应方法(单击,双击):       3.  掌握基本手势处理方法:

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

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

  8. Linux内核基础--事件通知链(notifier chain)

    转载: http://blog.csdn.net/wuhzossibility/article/details/8079025 http://blog.chinaunix.net/uid-277176 ...

  9. Linux内核基础--事件通知链(notifier chain)good【转】

    转自:http://www.cnblogs.com/pengdonglin137/p/4075148.html 阅读目录(Content) 1.1. 概述 1.2.数据结构 1.3.  运行机理 1. ...

随机推荐

  1. Expression 转化为sql(三) --自定义函数

    SQL 语句有很多函数如len(),now()等等.如何来生成这些函数.最近研究也写办法共大家参考. 一.首先建立一个建一个扩展类,控制只能允许这些函数出现,如果出现其他函数就直接报异常. publi ...

  2. CentOS 安装数据库笔记

    1.配置YUM源 # 下载mysql源安装包 shell.noarch.rpm # 安装mysql源 shell.noarch.rpm 检查mysql源是否安装成功 shell> yum rep ...

  3. 解决jmeter请求不成功或者报403错误

    有同学遇到这种情况,jmeter请求一个网站,各项参数填写正确,可是响应是403,同样的请求放在浏览器执行就没有问题: 这是因为被请求的网站做了请求来源过滤,来源不明的请求拒绝访问,我们需要在jmet ...

  4. js判断是否是ie浏览器且给出ie版本

    之前懒得写判断ie版本js,因为网上关于这方面的代码太多了,所以从网上拷贝了一个,放到项目上才发现由于时效性的问题,代码不生效.就自己写一个吧. 怎么去看浏览器的内核等信息 ---- js的全局对象w ...

  5. 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】

    平衡树初阶——AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...

  6. Python 任务队列 Celery

    一. celery 简介 Celery 是一个专注于实时处理和任务调度的分布式任务队列, 同时提供操作和维护分布式系统所需的工具.. 所谓任务就是消息, 消息中的有效载荷中包含要执行任务需要的全部数据 ...

  7. php面试问题

    问题:请用最简单的语言告诉我PHP是什么? 回答:PHP全称:Hypertext Preprocessor,是一种用来开发动态网站的服务器脚本语言. 问题:什么是MVC? 回答:MVC由Model(模 ...

  8. ecshop图片上传JPEG格式失败问题

    在根目录下找到includes文件目录,在其目录中找到cls_image.php打开并找到: $allow_file_types = '|GIF|JPG|JEPG|PNG|BMP|SWF|'; 此处J ...

  9. PHP 上传文件大小限制

    配置php.ini文件 (以上传500M以下大小的文件为例) 查找以下选项并修改-> file_uploads = On ;打开文件上传选项 upload_max_filesize = 500M ...

  10. 数据处理包dplyr的函数

    dplyr专注处理dataframe对象, 并提供更稳健的与其它数据库对象间的接口. 一.5个关键的数据处理函数: select()   返回列的子集filter()     返回行的子集arrang ...