作為入門級(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)事件:
- 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:
方法片择。- 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:
方法。- 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:
方法锰什。- 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:
方法。- 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è)概念相信大家都有了内舟,那么它有什么作用呢。要不然一堆理論沒有任何卵用初橘。
- 這里既然提到了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
- 既然我們可以知道事件的傳遞過程耕蝉,那么我們就能夠截獲這個(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ì)給你意想不到的驚喜。