關(guān)于響應(yīng)者鏈腮介,也就如此。

作為入門級(jí)的知識(shí)點(diǎn)端衰,其實(shí)還有很多人搞不清楚怎么回事叠洗,舉個(gè)例子:在一個(gè)父view加三個(gè)重疊的子視圖(UIView),給每一個(gè)view添加一個(gè)tap事件旅东,點(diǎn)擊重疊區(qū)域灭抑,響應(yīng)者毫無(wú)疑問是最上層的view,下面我們?cè)噧煞N情況:
1 設(shè)置最上層的view的userInteractionEnabled屬性為NO玉锌,你會(huì)發(fā)現(xiàn)第二層的view響應(yīng)了事件名挥。
2 設(shè)置最上層的view的userInteractionEnabled屬性為YES,去掉添加的tap事件主守,你會(huì)發(fā)現(xiàn)父view響應(yīng)了事件禀倔。
通過這個(gè)現(xiàn)象我們切入今天的主題:響應(yīng)者鏈的傳遞規(guī)則以及我們可以實(shí)施的陰謀。

引言

王老漢有兩個(gè)兒子参淫,他的大兒子是個(gè)光棍救湖,二兒子又兩個(gè)兒子,大致的關(guān)系圖如下:



有一天涎才,隔壁伍麗娟送來(lái)了一個(gè)肉包子鞋既,王老漢不舍得吃,于是問小王:“小王耍铜,你想吃嗎邑闺?你不想吃我問問你哥∽丶妫”陡舅,小王說(shuō):“我吃”,其實(shí)小王不是想自己吃伴挚,而是想給王大大和小明吃靶衍,于是問小明:“想吃嗎?”茎芋,小明天生是個(gè)植物人颅眶,根本不鳥他爹,也從來(lái)都沒鳥過田弥,于是問王大大涛酗,王大大說(shuō):“我吃!”,突然王大大發(fā)現(xiàn)包子里有個(gè)小紙條煤杀,他徹底懵逼了眷蜈,于是把這個(gè)有紙條的包子給了他爹也就是小王,小王也搞不定上面的暗語(yǔ)啊沈自,于是又交給了他爹老王酌儒,老王微微一笑,出門去解決了這個(gè)包子的問題枯途。
是不是覺得一派胡言忌怎,那就對(duì)了。


響應(yīng)者鏈的模式

首先酪夷,來(lái)了解兩個(gè)方法:

- (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;這個(gè)方法調(diào)用- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;并返回響應(yīng)事件的view榴啸。
比如如圖格局,點(diǎn)擊viewD晚岭,響應(yīng)事件:

  1. UIWindow知道有點(diǎn)擊事件之后鸥印,會(huì)首先調(diào)用自己的hitTest:方法,hitTest:這個(gè)方法會(huì)調(diào)用會(huì)調(diào)用自身的pointInside:方法坦报,結(jié)果發(fā)現(xiàn)pointInside:方法返回YES库说,說(shuō)明點(diǎn)擊區(qū)域在UIWindow內(nèi),然后UIWindow遍歷他的子視圖調(diào)用hitTest:方法片择。
  2. self.view調(diào)用自身的hitTest:方法潜的,hitTest:這個(gè)方法會(huì)調(diào)用會(huì)調(diào)用自身的pointInside:方法,結(jié)果發(fā)現(xiàn)pointInside:方法返回YES字管,從而確定點(diǎn)擊在self.view的范圍內(nèi)啰挪,然后self.view遍歷他的子視圖調(diào)用hitTest:方法。
  3. ViewC調(diào)用自身的hitTest:方法嘲叔,hitTest:這個(gè)方法會(huì)調(diào)用會(huì)調(diào)用自身的pointInside:方法亡呵,結(jié)果發(fā)現(xiàn)pointInside:方法返回YES,從而確定點(diǎn)擊在ViewC的范圍內(nèi)硫戈,然后ViewC遍歷他的子視圖調(diào)用hitTest:方法锰什。
  4. ViewE調(diào)用自身的hitTest:方法,hitTest:這個(gè)方法會(huì)調(diào)用會(huì)調(diào)用自身的pointInside:方法掏愁,結(jié)果發(fā)現(xiàn)pointInside:方法返回NO歇由,從而確定點(diǎn)擊不在ViewE的范圍內(nèi)卵牍,然后ViewE會(huì)在自己的hitTest:方法中返回nil果港;下一步輪到ViewD調(diào)用自身的hitTest:方法,hitTest:這個(gè)方法會(huì)調(diào)用會(huì)調(diào)用自身的pointInside:方法糊昙,結(jié)果發(fā)現(xiàn)pointInside:方法返回YES辛掠,從而確定點(diǎn)擊在ViewD的范圍內(nèi),然后ViewD遍歷他的子視圖調(diào)用hitTest:方法。
  5. viewD無(wú)子視圖所有遍歷終止萝衩,在方法- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;中返回viewD回挽,view一層層向superView傳遞,最終確定返回viewD猩谊,也就是viewD響應(yīng)事件;
    注意:如果某一層view的userInteractionEnabled設(shè)置為NO千劈,那么方法- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;會(huì)直接返回nil,所以事件到這里也就終止了牌捷。

實(shí)現(xiàn)過程可以這么理解:

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

既然確定了點(diǎn)擊的對(duì)象墙牌,那么下一步就是響應(yīng)事件,也就是利用剛才的反向順序暗甥,如果viewD不能響應(yīng)這個(gè)事件喜滨,那么便向上找,直到nextResponder響應(yīng)這個(gè)點(diǎn)擊事件撤防,如果都不響應(yīng)虽风,這個(gè)點(diǎn)擊事件便流失了。
其實(shí)一句話寄月,響應(yīng)者鏈就是一個(gè)從下到上的定位過程以及從上到下的尋找過程辜膝,定位的是點(diǎn)擊的view,尋找的是能夠響應(yīng)這個(gè)點(diǎn)擊的view剥懒。

這樣一個(gè)概念相信大家都有了内舟,那么它有什么作用呢。要不然一堆理論沒有任何卵用初橘。

  1. 這里既然提到了nextResponder验游,其實(shí)剛才說(shuō)的view是一個(gè)具象化的概念,因?yàn)閁IViewController也繼承自UIResponder保檐,那么說(shuō)一個(gè)通過view找到當(dāng)前屬于的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. 既然我們可以知道事件的傳遞過程耕蝉,那么我們就能夠截獲這個(gè)事件,讓我們看好的view去響應(yīng)這個(gè)事件夜只。比如:新手指導(dǎo)垒在,鏤空對(duì)應(yīng)區(qū)域并響應(yīng)鏤空區(qū)域點(diǎn)擊事件,點(diǎn)擊新手指導(dǎo)非鏤空區(qū)域提供事件接口扔亥。
//
//  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 是否可以響應(yīng)事件
 *modalViewsArray 需要模態(tài)的視圖數(shù)組
 *modalRectsArray 需要模態(tài)的rect數(shù)組
 */
@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;
/** 創(chuàng)建模態(tài) */
+(instancetype)YSModalViewOnSuperView:(UIView*)superView;
/** 修改屬性后刷新模態(tài) */
-(void)updateDisplay;
@end
//******************************************************
//******************************************************
//******************************************************
//
//  YSModalView.m
//  LibrarysDemo
//
//  Created by ys on 2017/4/26.
//  Copyright ? 2017年 ys. All rights reserved.
//
#import "YSModalView.h"
@implementation YSModalView
/** 創(chuàng)建模態(tài) */
+(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();
}
/** 修改屬性后刷新模態(tài) */
-(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

其實(shí)諸如此類的應(yīng)用時(shí)機(jī)還有很多场躯,基礎(chǔ)的的東西會(huì)也許會(huì)給你意想不到的驚喜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旅挤,一起剝皮案震驚了整個(gè)濱河市踢关,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粘茄,老刑警劉巖签舞,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秕脓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡儒搭,警方通過查閱死者的電腦和手機(jī)吠架,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搂鲫,“玉大人傍药,你說(shuō)我怎么就攤上這事』耆裕” “怎么了怔檩?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蓄诽。 經(jīng)常有香客問我薛训,道長(zhǎng),這世上最難降的妖魔是什么仑氛? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任乙埃,我火速辦了婚禮,結(jié)果婚禮上锯岖,老公的妹妹穿的比我還像新娘介袜。我一直安慰自己,他們只是感情好出吹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布遇伞。 她就那樣靜靜地躺著,像睡著了一般捶牢。 火紅的嫁衣襯著肌膚如雪鸠珠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天秋麸,我揣著相機(jī)與錄音渐排,去河邊找鬼。 笑死灸蟆,一個(gè)胖子當(dāng)著我的面吹牛驯耻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炒考,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼可缚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了斋枢?” 一聲冷哼從身側(cè)響起帘靡,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杏慰,沒想到半個(gè)月后测柠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缘滥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年轰胁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朝扼。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赃阀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出擎颖,到底是詐尸還是另有隱情榛斯,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布搂捧,位于F島的核電站驮俗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏允跑。R本人自食惡果不足惜王凑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聋丝。 院中可真熱鬧索烹,春花似錦、人聲如沸弱睦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)况木。三九已至垒拢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間火惊,已是汗流浹背子库。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矗晃,地道東北人仑嗅。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像张症,于是被迫代替她去往敵國(guó)和親仓技。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容