2017.05.08 20:40* 字数 1306 阅读 740评论 6喜欢 9

工作接近一年,很久没有更新博客。工作中学到很多知识点后面将花时间整理,作为对一年知识学习的总结:

下面是本篇博客的写作思路:

iOS-Responder Chain.png

人与计算机交互

目前计算机在我们生活中扮演很重要的角色,我们与计算机之间的交互也很普遍。多数情况使用最多的是 PC 和 移动端,而两种交互方式有很大的不同

  • PC 与人的交互
  • 移动端与人的交互

a) 在 PC 端我们通过键盘、鼠标等来对界面的内容进行操作和完成相关的任务

b)在移动端我们可以通过手指对界面内容进行点击控件和实现相应的操作

这里提出一个疑问,PC 端我们通过点击可以实现控件的相关操作。但是移动端我们手指点击控件是怎么被检测点击的位置、传递信息和做出相应的操作呢?

这里引入一个概念:Responder Chain (响应者链)

Responder Chain (响应者链)

  • NSResponder.h 头文件的源码
  • UIKit 控件之间的继承关系

NSResponder.h 头文件的源码

在我们点击手机屏幕的一个控件时,与 Responder Chain (响应者链)之间联系紧密。下面是小编在 UIKit 框架中找到相应相关的代码,如下:

@interface UIResponder : NSObject <UIResponderStandardEditActions>

@property(nonatomic, readonly, nullable) UIResponder *nextResponder;

- (nullable UIResponder*)nextResponder;

@property(nonatomic, readonly) BOOL canBecomeFirstResponder;    // default is NO

- (BOOL)canBecomeFirstResponder;    // default is NO

- (BOOL)becomeFirstResponder;

@property(nonatomic, readonly) BOOL canResignFirstResponder;

- (BOOL)canResignFirstResponder;    // default is YES

- (BOOL)resignFirstResponder;

@property(nonatomic, readonly) BOOL isFirstResponder;

- (BOOL)isFirstResponder;

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (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;

- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

在上面 <UIKit/UIResponder.h> 的文件中我们可以看出 UIResponder 的类可以实现 Touch 和 Press 相关操作的做出监听

UIKit 控件之间的继承关系

下面是小编对 UIKit 框架中点击相应控件有关 Class (类)继承关系整理,如下图:

iOS-Responder Chain-UIKit.png

由上面我们可以看出,在界面上我们平时使用控件时通过继承 UIResponder 来实现对界面 Touch 和 Press 相关操作的做出监听

响应链的创建

  • 控制器中的 View 树状结构
  • 点击相应的测试&打印

控制器中的View树状结构

在手机的 GUI 中我们多数情况下使用 UIViewController 和 UINavigationViewController 控制器来实现界面控件的管理,其中以控制器的 View 为界面显示创建和添加控件,最后形成 View 的控件树状结构

GUI 手机界面中,我们知道 UIView 中用 property (属性) superView 可以用来添加新的 UIView 到当前的 View 中,添加的子类 View 中 property (属性) nextResponder 可以指向父类 View 的 property (属性) superView

每一个 ViewController 中有 property(属性) view -> 既self.view 来进行添加子类 View,同样:view 的 property (属性) 通过 property (属性) nextResponder 来指向 ViewController

下图是 View 初始化布局:

iOS-Responder Chain-View-Front.png

iOS-Responder Chain-View-Hierarchy.png

点击相应的测试&打印

在定义的 ViewA1 中重写touch方法:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

UIResponder *next = [self nextResponder];

NSMutableString *prefix  = @"".mutableCopy;

while (nil != next) {

NSLog(@"%@%@", prefix, [next class]);

[prefix appendString:@"--"];

next = [next nextResponder];

}

}

点击 ViewA1 得到的打印结果:

2017-05-08 23:55:12.278 DesignPattern-Create[19441:727956] ViewA0

2017-05-08 23:55:12.278 DesignPattern-Create[19441:727956] --ViewA

2017-05-08 23:55:12.278 DesignPattern-Create[19441:727956] ----UIView

2017-05-08 23:55:12.279 DesignPattern-Create[19441:727956] ------ViewController

2017-05-08 23:55:12.279 DesignPattern-Create[19441:727956] --------UIWindow

2017-05-08 23:55:12.279 DesignPattern-Create[19441:727956] ----------UIApplication

2017-05-08 23:55:12.280 DesignPattern-Create[19441:727956] ------------AppDelegate

基于上面:小编从上面代码打印的结果可以验证,子 View 的 property(属性) nextResponder 指向父 View,父类的 View 的property(属性) nextResponder 最后指向 ViewController

如何找打第一响应者

  • 探测器 Hit - Test
  • 寻找第一相应的探测猜想

探测器Hit - Test

在响应链查找过程中,有两个函数起到很重要的作用

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

在响应链创建的情况下,我们在把寻找响应者的过程称为:Hit - Testing View 在寻找相应 View 的过程称为:Hit - Test

我们把 Hit - Test 可以比作一个探测器,通过这个探测器来检测手指点击在哪个 UIView 上面

通过上面的继承关系和点击后打印出的结果可以看到,当我们点击界面 UIApplication 就会调用 hitTest: withEvent 查看点击是否在 UIWindow 中如果不在就返回 nil , 如果在 UIWindow 也会调用 hitTest: withEvent 对UIWindow 中 subViews 进行探测

hitTest: withEvent 在探测过程中采用我们比较常见 递归的方式来查找点击 UIView, 通过我们在初始化过程中最后被初始化的放在最上面

在点击 ViewA1 来进行验证下一个 nextResponder 过程中在每一个 View重写 hitTest 如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

if (!self.isUserInteractionEnabled || self.hidden || self.alpha <= 0.01) {

return nil;

}

NSLog(@"ResponderChain : %@", [self class]);

if ([self pointInside:point withEvent:event]) {

for (UIView *subView in [self.subviews reverseObjectEnumerator]) {

CGPoint converPoin = [subView convertPoint:point fromView:self];

UIView *hitTestView = [subView hitTest:converPoin withEvent:event];

if (hitTestView) {

NSLog(@"ViewA : hitTestView -> %@", [hitTestView class]);

return hitTestView;

}

}

return self;

}

return nil;

}

点击 ViewA1 得到打印的结果如下:

2017-05-09 07:42:05.968 DesignPattern-Create[1197:22554] ResponderChain : ViewB

2017-05-09 07:42:05.969 DesignPattern-Create[1197:22554] ResponderChain : ViewA

2017-05-09 07:42:05.969 DesignPattern-Create[1197:22554] ResponderChain : ViewA0

2017-05-09 07:42:05.969 DesignPattern-Create[1197:22554] ResponderChain : ViewA1

寻找第一相应的探测猜想

根据打印的结果,我们可以验证点击具体查找的流程如下图所示:(实例是点击 viewA1)

UIApplication —> UIWindow —> viewB —> viewA —> viewA0 —> viewA1

iOS-Responder Chain-Hit-Testing-View.png

利用响应者在应用中的体现

  • 改变控件的相应范围

改变控件的相应范围

我们可以利用 hitTest 根据 point 方式来重新设置 ponit 的点击相应的范围大小

在 ViewB1 中实现点击相应范围在布局显示基础上下左右拓展 10 px,实现代码如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {

return nil;

}

NSLog(@"ResponderChain : %@", [self class]);

CGRect touchRect = CGRectInset(self.bounds, -10, -10);

if (CGRectContainsPoint(touchRect, point)) {

for (UIView *subView in [self.subviews reverseObjectEnumerator]) {

CGPoint converPoint = [subView convertPoint:point fromView:self];

UIView *hitTestView = [subView hitTest:converPoint withEvent:event];

if (hitTestView) {

NSLog(@"ViewB1 : hitTestView -> %@", [hitTestView class]);

return hitTestView;

}

}

return self;

}

return nil;

}

ViewB1 界面中实际范围,和点击相应的 viewB1 外的虚框的范围也会做出相应的相应。如下图:

iOS-Responder Chain-Apply.png

打印的结果:

2017-05-09 10:34:00.437 DesignPattern-Create[1197:22554] ResponderChain : ViewB

2017-05-09 10:34:00.437 DesignPattern-Create[1197:22554] ResponderChain : ViewB1

2017-05-09 10:34:00.437 DesignPattern-Create[1197:22554] ViewB : hitTestView -> ViewB1

Dome下载地址

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

  1. iOS 响应链

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

  2. iOS响应链和传递机制

    iOS中加载的时候会先执行main函数 int main(int argc, charchar * argv[]) { @autoreleasepool { return UIApplicationM ...

  3. iOS响应链原理

    ios找到被点击的view的过程是从根view开始递归地调用hitTest方法,直到有一个子view的hitTest方法返回自身:如果所有一级子view的hitTest方法都返回nil,那么根view ...

  4. 【IOS笔记】Event Delivery: The Responder Chain

    Event Delivery: The Responder Chain  事件分发--响应链 When you design your app, it’s likely that you want t ...

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

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

  6. hitTest:WithEvent 和Responder Chain

    这个方法是找到那个View被touch,当找到后就成为响应链的第一个了,如果他不能处理这个Event,那么就找nextResponder 直至application 如果不能处理,那就会丢弃掉. ht ...

  7. Event Handling Guide for iOS--(三)---Event Delivery: The Responder Chain

    Event Delivery: The Responder Chain 事件传递:响应链 When you design your app, it’s likely that you want to ...

  8. 响应链和UIKit框架

    Event Delivery: The Responder Chain When you design your app, it’s likely that you want to respond t ...

  9. iOS事件响应链(Responder Chain)

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

随机推荐

  1. jQuery入门(1)

    1.了解jQuery与JavaScript的区别 css --你的长相啦 Html --躯干 js --运动神经 jQuery就是对JavaScript的一个拓展,封装,就是让JavaScript更好 ...

  2. python中根据字符串导入模块module

    python中根据字符串导入模块module 需要导入importlib,使用其中的import_module方法 import importlib modname = 'datetime' date ...

  3. FCM算法的matlab程序

    FCM算法的matlab程序 在“FCM算法的matlab程序(初步)”这篇文章中已经用matlab程序对iris数据库进行简单的实现,下面的程序最终的目的是求准确度. 作者:凯鲁嘎吉 - 博客园 h ...

  4. Linux 基本操作--文件查看 (day3)

    一.查看文件-----cat (详情参考:http://blog.sina.com.cn/s/blog_52f6ead0010127xm.html) 语法结构: cat 查看方式 文件 cat  -A ...

  5. rem实现自适应

    总结一下,rem实现自适应:用rem代替px,配合媒体查询设置font-size:n%. 首先,px是死的.若一律用px,那就会大的显得小,小的屏幕显得大.其次,rem是活的,通过设置字体大小可以引起 ...

  6. C#进阶のMEF注入

    1.什么是MEF 先来看msdn上面的解释:MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库. 应用程序开发人员可利用该库发现并使用扩展, ...

  7. fabric使用

    1.入门博客https://fabric-chs.readthedocs.io/zh_CN/chs/tutorial.html 如果遇到这个问题说明你的fabric版本太高了 卸载到现在版本重新安装就 ...

  8. 解决y7000笔记本ubuntu下wifi无法连接问题

    查看wifi与蓝牙硬件开关,发现ideapad的硬件模块都是关闭的 rfkill list all 打开终端 输入 sudo gedit /etc/rc.local 写入以下内容 进行保存 #!/bi ...

  9. 生成对抗网络(GAN)

    GAN的全称是 Generative Adversarial Networks,中文名称是生成对抗网络.原始的GAN是一种无监督学习方法,巧妙的利用“博弈”的思想来学习生成式模型. 1 GAN的原理 ...

  10. 【转】android SDK中的ddms使用详解

    一.查看线程信息1.展开左侧设备节点,选择进程: 2.点击更新线程信息图标: 注意:如果你没有运行或调试程序的话,这些图标是不可用的! 3.右侧选择“Threads”标签: 二.查看堆栈信息1.展开左 ...