事件的 传递链 响应链 (ios)

前言(铺垫一下)

当发生事件时必须知道由谁来响应事件。

所有事件响应的类都是UIResponder的子类,

响应链是一个由不同对象组成的层次结构,
其中的每个对象将依次获得响应事件消息的机会。

当发生事件时 事件首先被发送给第一响应者,
第一响应者往往是事件发生的视图,也就是用户触摸屏幕的地方。

事件将沿着响应者链一直向下传递,直到被接受并做出处理。

一般来说第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理事件就会被传递给它的视图控制器对象viewcontroller(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow对象)再到程序(UIApplication对象)。

如果整个过程都没有响应这个事件,该事件就被丢弃。
在响应者链中只要由对象处理事件,事件就停止传递。

典型的 事件响应链 路线图:
由离用户最近的view向系统传递。
First Responser -- >View -- >SuperView...-- >The Window -- >The Application -- > App Delegate

响应者链流程经常被委托(delegation)打断,

一个对象(通常是视图)可能将响应工作委托给另一个对象来完成(通常是视图控制器ViewController),所以 做事件响应时在ViewController中必须实现相应协议来实现事件委托。

iOS中的UIResponder类,定义了响应者对象的所有方法。UIApplication、UIView等类都继承了UIResponder类,UIWindow和UIKit中的控件因为继承了UIView,所以也间接继承了UIResponder类,这些类的实例都可以当作响应者。




下图网络出处:https://www.cnblogs.com/Julday/archive/2019/12/30/12119536.html

事件的(传递,响应).png

iOS事件的类型

iOS用户操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。

对应的事件类型如下:
1、触屏事件 (Touch Event)
2、运动事件 (Motion Event)
3、远端控制事件(Remote-Control Event)

惯例我们就以触屏事件Touch Event为例说明在Cocoa Touch框架中事件的处理流程。

响应链(Responder Chain)

响应者对象(Responder Object)指的是有响应和处理事件能力的对象。
响应链 就是由一系列的 响应者对象 构成的一个层次结构。

UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、 UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。


响应链的基本构成.png

上图可见 响应者链的一些特点:
1、响应者链通常是由视图(UIView)构成的;
2、视图的下一个响应者是它de视图控制器UIViewController(当然如果有它的话),然后再转给它的父视图Super View;
3、视图控制器 的下一个响应者shi其管理的视图的父视图;
4、单例的窗口UIWindow的内容视图将指向窗口本身作为它的下一个响应者;
5、单例的应用UIApplication是一个响应者链的终点,它的下一个响应者指向nil,结束整个循环。

Cocoa应用可以有多个UIWindow对象。
Cocoa Touch应用只有一个UIWindow对象,整个响应者链比较简单。

事件分发(Event Delivery)

第一响应者First responder指的是当前接受触摸的响应者对象(通常是一个UIView对象),即当前该对象正在与用户交互,它是响应者链的开端。

事件传递的最终目的:找出一个能处理并响应事件的对象(第一响应者First responder)

事件传递的流程图.png

如何寻找第一响应者(事件传递的过程)

事件传递链:
由系统向离用户最近的view传递。
UIKit –> active app's event queue –> window –> root view –> …… –> lowest view

1、当iOS程序发生触摸事件后,系统会利用Runloop将事件加入到UIApplication的任务队列中,具体过程可以参考深入理解RunLoop
2、UIApplication分发触摸事件到UIWindow,然后UIWindow依次向下分发给UIView
3、UIView调用hitTest:withEvent:方法看看自己能否处理事件,以及触摸点是否在自己上面。
4、如果满足条件,就遍历UIView上的子控件。重复上面的动作。
5、直到找到最顶层的一个满足条件(既能处理触摸事件,触摸点又在上面)的子控件,此子控件就是我们需要找到的第一响应者。

hitTest:withEvent:的处理流程

(上面的查找其实就是由该方法递归调用实现的)
1、首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
2、若返回NO,则hitTest:withEvent:返回nil;
3、若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
4、若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
5、如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

hitTest:withEvent:方法的伪代码大致如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.userInteractionEnabled || !self.hidden || self.alpha <= 0.01) {
        return nil;
    }
    
    if ([self pointInside:point withEvent:event]) {
        
        for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
            CGPoint subPoint = [subView convertPoint:point fromView:self];
            
            UIView *bestView = [subView hitTest:subPoint withEvent:event];
            if (bestView) {
                return bestView;
            }
        }
        return self;
    }

    return nil;
}

事件的响应流程

通过上面的 hitTest:withEvent: 寻找到第一响应者后,需要逆着寻找第一响应者的方向(从第一响应者->UIApplication)来响应事件。

流程如下

1.首先通过 hitTest:withEvent: 确定第一响应者,以及相应的响应链
2.判断第一响应者能否响应事件,如果第一响应者能进行响应则事件在响应链中的传递终止。如果第一响应者不能响应则将事件传递给 nextResponder也就是通常的superview进行事件响应
3.如果事件继续上报至UIWindow并且无法响应,它将会把事件继续上报给UIApplication
4.如果事件继续上报至UIApplication并且也无法响应,它将会将事件上报给其Delegate
5.如果最终事件依旧未被响应则会被系统抛弃

哪些视图不响应呢?

hidden = YES 视图被隐藏
userInteractionEnabled = NO 不接受响应事件
alpha <= 0.01,透明视图不接收响应事件
子视图超出父视图范围
需响应视图被其他视图盖住
是否重写了其父视图以及自身的hitTest方法
是否重写了其父视图以及自身的pointInside方法

应用场景

方形按钮点击四角无效,点击中间的圆形区域有效。
核心思路:在pointInside: withEvent:方法中修改对应的区域。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 如果控件不允许与用用户交互,那么返回nil
    if (!self.userInteractionEnabled || [self isHidden] || self.alpha <= 0.01) {
        return nil;
    }

    //判断当前视图是否在点击范围内
    if ([self pointInside:point withEvent:event]) {
        //遍历当前对象的子视图(倒序)
        __block UIView *hit = nil;
        [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            //坐标转换,把当前坐标系上的点转换成子控件坐标系上的点
            CGPoint convertPoint = [self convertPoint:point toView:obj];
            //调用子视图的hitTest方法,判断自己的子控件是不是最适合的View
            hit = [obj hitTest:convertPoint withEvent:event];
            //如果找到了就停止遍历
            if (hit) *stop = YES;
        }];

        //返回当前的视图对象
        return hit?hit:self;
    }else {
        return nil;
    }
}

// 该方法判断触摸点是否在控件身上,是则返回YES,否则返回NO,point参数必须是方法调用者的坐标系
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {   
    CGFloat x1 = point.x;
    CGFloat y1 = point.y;
    
    CGFloat x2 = self.frame.size.width / 2;
    CGFloat y2 = self.frame.size.height / 2;
    
    //判断是否在圆形区域内
    double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    if (dis <= self.frame.size.width / 2) {
        return YES;
    }
    else{
        return NO;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353