响应者链条和手势识别器

相关资源

GitHub粒子发射和复制图层示例
GitHub粘性控件示例
GitHub弹性动画
CALayer分析
CAEmitter分析

简介

  1. Responder Chain for Action Messages

    macOS Cocoa Application,macOS开发使用的到的响应者知识。

  2. Responder Chain for Event Messages

    UIApplication,iOS开发使用的到的响应者知识,这里重点介绍。应用程序接收和处理事件使用响应对象。接受对象是任何实例UIResponder类和普通子类,包括UIView,UIViewController,UIApplication。UIKit自动管理大多数相应相关的行为,包括事件是如何从一个响应者传送到下一个响应者。当然,您可以修改默认行为以更改事件在应用程序中的传递方式。

    UIKit将大部分事件(很多种类的事件)传递给最恰当的响应者对象。如果对象不处理该事件,UIKit转发给在活跃的响应者链条中的下一个响应者,这是一个动态的应用程序的响应对象的配置。因为它是动态的,在你的应用程序中没有单一的响应链。然而,它很容易确定在链中的下一个响应器,因为事件总是从特定的响应对象流到更一般的响应对象。例如,一个视图的下一个响应者要么是它的父视图要么是管理它的视图控制器。事件一直向响应链流,直到它们被处理为止。

    分析:UIKit帮我们做了默认的事件传递,然而我们可以修改默认行为,比如,nextResponder属性、在touchesBegan: withEvent:中修改。

事件类型

对于每一个事件,UIKit会制定一个第一响应者(first responder ),并且把事件首先传递给该对象。第一响应者根据以下事件类型变化而变化。

  1. Touch events:第一响应者就是触摸发生所在的控件。
  2. Press events:第一响应者就是获取焦点的响应者(see App Programming Guide for tvOS)
  3. Motion events:第一响应者就是你指定处理事件的响应者.
  4. Shake-motion events. 第一响应者就是你(or UIKit)指定处理事件的响应者。
  5. Remote-control events: 第一响应者就是你(or UIKit)指定处理事件的响应者。
  6. Editing-menu messages:第一响应者就是你(or UIKit)指定处理事件的响应者。For information about the UIKit editing commands, see UIResponderStandardEditActions.

获取Touch事件的第一响应者

UIKit使用基于视图View的hit测试(点击测试)来确定触摸事件的发生地。具体来说,UIKit会对比触摸位置和View对象的范围(这里理解为frame)。UIView对象的hitTest:withEvent:方法会穿越视图层次寻找最深的包含指定触摸的子视图。该视图成为触摸事件的第一响应者。

1.图片分析

对于触摸事件的响应,首先要找到能够响应该事件的对象,iOS是用hit-testing 来找到哪个视图被触摸了(hit-test view),也就是以keyWindow为起点,hit-test view为终点,逐级调用hitTest:withEvent。


MJ大神的图

2.hitTest:withEvent:方法内部的处理流程:

  1. 首先判断自己能否接收事件,以下情况视图的hitTest:withEvent:方法会返回nil,导致自身和其所有子视图不能被hit-testing发现:

    • 隐藏(hidden=YES)的视图
    • 禁止用户操作(userInteractionEnabled=NO)的视图
    • 视图View.alpha < 0.01的视图
    • 视图超出父视图的区域
  2. 再用调用pointInside:withEvent:判断触摸点是否在当前视图内:

    • 如果返回YES,那么该视图的所有子视图调用hitTest:withEvent,调用顺序由层级低到高(top->bottom)依次调用。
    • 如果返回NO,说明触摸不在控件内,那么hitTest:withEvent返回nil,该视图的所有子视图的分支全部被忽略。
  3. 在此视图的pointInside:withEvent:返回YES的情况下,并且他的所有子视图hitTest:withEvent:都返回nil,或者该视图没有子视图,那么该视图的hitTest:withEvent:返回自己。

  4. 在此视图的pointInside:withEvent:返回YES的情况下,如果子视图的hitTest:withEvent:返回非空对象,那么当前视图的hitTest:withEvent:也返回这个对象,也就是沿原路回推,最终将hit-test view传递给keyWindow。

3 代码示例(系统默认原理模拟)

- (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 = self.subviews.count;
    for (int i = count - 1; i >= 0; i--) {
        // 获取子控件
       UIView *childView = self.subviews[i];
        
        // 把当前坐标系上的点转换成子控件上的点
      CGPoint childP =  [self convertPoint:point toView:childView];
        
       UIView *fitView = [childView hitTest:childP withEvent:event];
        
        if (fitView) {
            return fitView;
        }  
    }
    
    // 4.如果没有比自己合适的子控件,最合适的view就是自己
    return self;
    
}

// 判断下当前这个点在不在方法调用者上(我们还可以重写此方法改变寻找结果)
// 使用注意点:点必须是方法调用者上的坐标系,才会判断准备
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//    [super pointInside:point withEvent:event];
//    return NO;
//}

Tip:我们可以通过自定义- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 方法忽略前两个判断做到意想不到的效果。代码如下:

// 处理不在此控件上但是在子控件上的点(超出本控件的子控件的部分)
 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    
    // 判断下point点在不在特定子控件上(这个点明显不在此控件上)
    CGPoint chatP = [self convertPoint:point toView:子控件];
    if ([子控件 pointInside:chatP withEvent:event] == YES) {
        return self.chatView;
    } 
    return [super hitTest:point withEvent:event];
    
}

响应者链条(responder chain)

responderChain.png
  1. 事件往上传递(nextResponder)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:  (UIEvent *)event {
  [super touchesBegan:touches withEvent:event];
  NSLog(@"%s",__func__);
  NSLog(@"nextResponder = %@",self.nextResponder);
}
  1. 重置nextResponder
- (UIResponder *)nextResponder {
   return [super nextResponder];
   }
  1. 我们可以通过以下两种方式中断响应者链条

    - (void)touchesBegan:(NSSet<UITouch *> *)touches    withEvent:(UIEvent *)event {
    NSLog(@"nextResponder = %@",self.nextResponder);
    }
    
     - (UIResponder *)nextResponder {
    return nil;
    }
    
    

手势识别器(UIGestureRecognizer)

简介

手势识别器是处理view中touch or press events的一种简单方式,我们可以为一个控件添加多种识别器。它把事件和处理逻辑封装成了一种简单方便的形式。

gestureRecognizer.png

手势识别器有两种类型:离散和连续。手势一旦被识别后,一个离散手势识别器会调用你的动作方法。一个连续的手势识别器可以多次调用你的动作方法,包括手势的开始和结束,以及每次跟踪事件的细节变化。例如,一个uipangesturerecognizer对象每当一个触摸事件的位置变化都会调用你的动作方法。
界面生成器(nterface Builder)内部包含每个标准UIKit手势识别的对象。它还包括一个自定义手势识别对象,您可以使用它作为你的自定义的UIGestureRecognizer子类。

分析

  1. 由于UIGestureRecognizer处理的是事件相关,所以它也是遵循hitTest: withEvent:方法找到第一响应者。
  2. 响应者链条这里并不适用UIGestureRecognizer。如果一个控件成为第一响应者,那么如果它身上没有添加相应的手势识别器,就会查找它的父控件(而不是它的nextResponder,通过中断响应者链条测试)能不能match相应的UIGestureRecognizer。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 好奇触摸事件是如何从屏幕转移到APP内的?困惑于Cell怎么突然不能点击了?纠结于如何实现这个奇葩响应需求?亦或是...
    Lotheve阅读 56,614评论 51 597
  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 5,987评论 4 26
  • 概述 应用程序使用响应者对象来接收和处理事件,属于UIResponder类的实例对象都是响应者,常见的子类包括UI...
    渐z阅读 2,635评论 0 3
  • -- iOS事件全面解析 概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实...
    翘楚iOS9阅读 2,942评论 0 13
  • 什么是事件? iOS中事件分为3大类 : 触摸事件, 加速计事件和远程控制事件.当你的手指在手机屏幕上触摸时, 产...
    iYeso阅读 157评论 0 1