关于响应者链,也就如此。

作为入门级的知识点,其实还有很多人搞不清楚怎么回事,举个例子:在一个父view加三个重叠的子视图(UIView),给每一个view添加一个tap事件,点击重叠区域,响应者毫无疑问是最上层的view,下面我们试两种情况:
1 设置最上层的view的userInteractionEnabled属性为NO,你会发现第二层的view响应了事件。
2 设置最上层的view的userInteractionEnabled属性为YES,去掉添加的tap事件,你会发现父view响应了事件。
通过这个现象我们切入今天的主题:响应者链的传递规则以及我们可以实施的阴谋。

引言

王老汉有两个儿子,他的大儿子是个光棍,二儿子又两个儿子,大致的关系图如下:



有一天,隔壁伍丽娟送来了一个肉包子,王老汉不舍得吃,于是问小王:“小王,你想吃吗?你不想吃我问问你哥。”,小王说:“我吃”,其实小王不是想自己吃,而是想给王大大和小明吃,于是问小明:“想吃吗?”,小明天生是个植物人,根本不鸟他爹,也从来都没鸟过,于是问王大大,王大大说:“我吃!”,突然王大大发现包子里有个小纸条,他彻底懵逼了,于是把这个有纸条的包子给了他爹也就是小王,小王也搞不定上面的暗语啊,于是又交给了他爹老王,老王微微一笑,出门去解决了这个包子的问题。
是不是觉得一派胡言,那就对了。


响应者链的模式

首先,来了解两个方法:

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

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;这个方法调用- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;并返回响应事件的view。
比如如图格局,点击viewD,响应事件:

  1. UIWindow知道有点击事件之后,会首先调用自己的hitTest:方法,hitTest:这个方法会调用会调用自身的pointInside:方法,结果发现pointInside:方法返回YES,说明点击区域在UIWindow内,然后UIWindow遍历他的子视图调用hitTest:方法。
  2. self.view调用自身的hitTest:方法,hitTest:这个方法会调用会调用自身的pointInside:方法,结果发现pointInside:方法返回YES,从而确定点击在self.view的范围内,然后self.view遍历他的子视图调用hitTest:方法。
  3. ViewC调用自身的hitTest:方法,hitTest:这个方法会调用会调用自身的pointInside:方法,结果发现pointInside:方法返回YES,从而确定点击在ViewC的范围内,然后ViewC遍历他的子视图调用hitTest:方法。
  4. ViewE调用自身的hitTest:方法,hitTest:这个方法会调用会调用自身的pointInside:方法,结果发现pointInside:方法返回NO,从而确定点击不在ViewE的范围内,然后ViewE会在自己的hitTest:方法中返回nil;下一步轮到ViewD调用自身的hitTest:方法,hitTest:这个方法会调用会调用自身的pointInside:方法,结果发现pointInside:方法返回YES,从而确定点击在ViewD的范围内,然后ViewD遍历他的子视图调用hitTest:方法。
  5. viewD无子视图所有遍历终止,在方法- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;中返回viewD,view一层层向superView传递,最终确定返回viewD,也就是viewD响应事件;
    注意:如果某一层view的userInteractionEnabled设置为NO,那么方法- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;会直接返回nil,所以事件到这里也就终止了。

实现过程可以这么理解:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    if (self.userInteractionEnabled) {
        if([self pointInside:point withEvent:event]){
            for (UIView *view in self.subviews) {
                UIView *hitTestView = [view hitTest:point withEvent:event];
                if(!hitTestView){
                    return view;
                }
            }
        }
    }
    return nil;
}

既然确定了点击的对象,那么下一步就是响应事件,也就是利用刚才的反向顺序,如果viewD不能响应这个事件,那么便向上找,直到nextResponder响应这个点击事件,如果都不响应,这个点击事件便流失了。
其实一句话,响应者链就是一个从下到上的定位过程以及从上到下的寻找过程,定位的是点击的view,寻找的是能够响应这个点击的view。

这样一个概念相信大家都有了,那么它有什么作用呢。要不然一堆理论没有任何卵用。

  1. 这里既然提到了nextResponder,其实刚才说的view是一个具象化的概念,因为UIViewController也继承自UIResponder,那么说一个通过view找到当前属于的VC的方法:
#import "UIView+Responder.h"
@implementation UIView (Responder)
-(UIViewController*)viewOnCurrentVC{
    UIResponder *responder = [self nextResponder];
    while (responder) {
        if ([responder isKindOfClass:[UIViewController class]]) {
            return (UIViewController*)responder;
        }
        responder = [responder nextResponder];
    }
    return nil;
}
@end
  1. 既然我们可以知道事件的传递过程,那么我们就能够截获这个事件,让我们看好的view去响应这个事件。比如:新手指导,镂空对应区域并响应镂空区域点击事件,点击新手指导非镂空区域提供事件接口。
//
//  YSModalView.h
//  LibrarysDemo
//
//  Created by ys on 2017/4/26.
//  Copyright © 2017年 ys. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface YSModalView : UIView
/*
 *strokeColor 边框颜色
 *canRespond 是否可以响应事件
 *modalViewsArray 需要模态的视图数组
 *modalRectsArray 需要模态的rect数组
 */
@property(nonatomic,strong)UIColor *strokeColor;
@property(nonatomic,assign)BOOL canRespond;
@property(nonatomic,copy)void(^tapModalBlock)();
@property(nonatomic,strong)NSArray *modalViewsArray;
@property(nonatomic,strong)NSArray<NSValue*> *modalRectsArray;
/** 创建模态 */
+(instancetype)YSModalViewOnSuperView:(UIView*)superView;
/** 修改属性后刷新模态 */
-(void)updateDisplay;
@end
//******************************************************
//******************************************************
//******************************************************
//
//  YSModalView.m
//  LibrarysDemo
//
//  Created by ys on 2017/4/26.
//  Copyright © 2017年 ys. All rights reserved.
//
#import "YSModalView.h"
@implementation YSModalView
/** 创建模态 */
+(instancetype)YSModalViewOnSuperView:(UIView*)superView{
    if (!superView) {
        return nil;
    }
    YSModalView *modalView = [[YSModalView alloc] initWithFrame:superView.bounds];
    modalView.userInteractionEnabled = YES;
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:modalView action:@selector(tapModalView:)];
    [modalView addGestureRecognizer:tap];
    modalView.backgroundColor = [UIColor clearColor];
    [superView addSubview:modalView];
    return modalView;
}
-(void)tapModalView:(UITapGestureRecognizer*)tap{
    if (self.tapModalBlock) self.tapModalBlock();
}
/** 修改属性后刷新模态 */
-(void)updateDisplay{
    [self setNeedsDisplay];
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    if (self.canRespond) {
        for (NSValue *viewValue in [self allRectArray]) {
            CGRect viewRect = viewValue.CGRectValue;
            viewRect = [self convertRect:viewRect toView:self];
            if (CGRectContainsPoint(viewRect, point)) {
                return NO;
            }
        }
    }
    return YES;
}
-(void)drawRect:(CGRect)rect{
    UIBezierPath *backBezierPath = [UIBezierPath bezierPathWithRect:self.bounds];
    backBezierPath.usesEvenOddFillRule = YES;
    backBezierPath.lineWidth = 0;
    //
    UIBezierPath *strokePath = [UIBezierPath bezierPath];
    strokePath.lineWidth = 2;
    for (NSValue *viewValue in [self allRectArray]) {
        //按钮镂空位置
        CGRect viewRect = viewValue.CGRectValue;
        viewRect = [self convertRect:viewRect toView:self];
        UIBezierPath *viewPath = [UIBezierPath bezierPathWithRoundedRect:viewRect cornerRadius:5];
        [backBezierPath appendPath:viewPath];
        //虚线
        UIBezierPath *oneStrokePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(viewRect.origin.x - 2, viewRect.origin.y - 2, viewRect.size.width + 4, viewRect.size.height + 4) cornerRadius:5];
        [strokePath appendPath:oneStrokePath];
    }
    [[[UIColor blackColor] colorWithAlphaComponent:0.5] setFill];
    [backBezierPath fill];
    //
    CGFloat dash[] = {5,3};
    [strokePath setLineDash:dash count:2 phase:0];
    [self.strokeColor setStroke];
    [strokePath stroke];
}
//
-(NSArray<NSValue*>*)allRectArray{
    NSMutableArray* allRectArray = [NSMutableArray arrayWithArray:self.modalRectsArray];
    for (UIView* view in self.modalViewsArray) {
        [allRectArray addObject:[NSValue valueWithCGRect:view.frame]];
    }
    return allRectArray;
}
@end

其实诸如此类的应用时机还有很多,基础的的东西会也许会给你意想不到的惊喜。

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

推荐阅读更多精彩内容