前言
之前我已經(jīng)通過《史上最詳細(xì)的iOS之事件的傳遞和響應(yīng)機制-原理篇》比較詳細(xì)的介紹過了事件的響應(yīng)和傳遞的一些原理坡贺。如果說上篇是原理性文章渣玲,那么本篇文章更偏重于實踐器贩。本篇文章主要介紹如何利用事件處理的這些機制來處理公司開發(fā)中一些比較棘手的需求。例如蔬胯,點擊的是A視圖对供,卻要讓B視圖處理事件;點擊子視圖氛濒,卻要讓父視圖處理事件等等产场。今天,我整理了下之前的雜記泼橘,羅列出了一些開發(fā)中可能遇到的情景和應(yīng)對措施涝动!當(dāng)然,這要求我們對事件的傳遞和響應(yīng)機制非常了解炬灭。如果對此不太了解醋粟,請閱讀筆者的《史上最詳細(xì)的iOS之事件的傳遞和響應(yīng)機制-原理篇》。
如上圖重归,視圖層次結(jié)構(gòu):白色->紅色->綠色米愿。紅色的view是綠色view的父視圖,白色的view又是紅色view的父視圖鼻吮。如下要求:
需求情景一
當(dāng)子控件和父控件重疊時育苟,點擊子控件,子控件響應(yīng)事件椎木。也就是說违柏,點擊綠色的view和紅色的view的重疊部分,只有綠色的view響應(yīng)事件香椎。
分析
:其實這算不上一個需求漱竖,因為系統(tǒng)默認(rèn)就是這樣處理的。重寫綠色view的 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event方法畜伐,默認(rèn)就是綠色的view響應(yīng)事件馍惹,但是僅限于重疊部分,點擊綠色view上的非重疊部分,綠色和紅色view都不會響應(yīng)万矾。
原因在于悼吱,系統(tǒng)從window向下尋找最合適的view時候,遍歷到紅色的view時候良狈,發(fā)現(xiàn)點不在紅色的view上后添,那么默認(rèn)控制器的view就是最合適的view。即控制器的view響應(yīng)了事件们颜。
#import "GreenView.h"
@implementation GreenView
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"greenView %s",__func__);
}
@end
打印結(jié)果
:
2016-02-27 20:49:46.234 事件處理和響應(yīng)[905:44466] greenView -[GreenView touchesBegan:withEvent:]
2016-02-27 20:49:48.051 事件處理和響應(yīng)[905:44466] greenView -[GreenView touchesBegan:withEvent:]
需求情景二
當(dāng)子控件和父控件部分重疊時吕朵,點擊父子控件重疊部分,只有父控件響應(yīng)事件窥突。也就是說努溃,點擊綠色的view和紅色的view的重疊部分,只有紅色的view響應(yīng)事件阻问。
分析
:點擊子控件梧税,卻要讓父控件響應(yīng)事件敞恋,說明子控件本身不是最合適的view甸鸟,父控件才是最合適的view,因為hitTest:withEvent:方法的作用就是控件接收到事件后纷闺,判斷自己是否能處理事件刨秆,判斷點在不在自己的坐標(biāo)系上凳谦,然后返回最合適的view。所以衡未,我們可以在hitTest:withEvent:方法里面強制返回父控件為最合適的view尸执,也就是返回紅色的view。
注意缓醋,不能夠重寫父控件的hitTest:withEvent:方法如失,也就是不能夠重寫紅色的view的hitTest:withEvent:方法。原因在于送粱,如果重寫父控件的hitTest:withEvent:方法褪贵,并在該方法中返回父控件本身,會導(dǎo)致點擊父控件的父控件時抗俄,也是父控件為最合適的view脆丁。反應(yīng)在上面的例子上就是,點擊白色的地方动雹,也是紅色的view響應(yīng)事件偎快。
#import "GreenView.h"
@implementation GreenView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return [self superview]; // return nil; // 此處返回nil也可以。返回nil就相當(dāng)于當(dāng)前的view不是最合適的view
}
@end
打印結(jié)果
:
2016-02-27 20:52:09.083 事件處理和響應(yīng)[921:46215] redView -[RedView touchesBegan:withEvent:]
2016-02-27 20:52:10.154 事件處理和響應(yīng)[921:46215] redView -[RedView touchesBegan:withEvent:]
需求情景三
無論點擊什么地方洽胶,父控件響應(yīng)事件。也就是說,點擊白色的view姊氓、紅色的view或者綠色的view上的任一點(屏幕上任一點)丐怯,都只是紅色的view響應(yīng)事件。
分析
:點擊屏幕上任意點翔横,都是紅色的view響應(yīng)事件读跷,根據(jù)視圖層次結(jié)構(gòu),我們只需要重寫紅色View的hit:test:方法禾唁,并在此方法中返回紅色的view即可效览。
@implementation RedView
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"RedView %s",__func__);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return self;
}
@end
打印結(jié)果
:
2016-02-27 20:58:29.883 CharactersRange[4843:853272] RedView -[RedView touchesBegan:withEvent:]
2016-02-27 20:58:30.653 CharactersRange[4843:853272] RedView -[RedView touchesBegan:withEvent:]
2016-02-27 20:58:31.479 CharactersRange[4843:853272] RedView -[RedView touchesBegan:withEvent:]
2016-02-27 20:58:32.199 CharactersRange[4843:853272] RedView -[RedView touchesBegan:withEvent:]
需求情景四
當(dāng)子控件和父控件部分重疊時,點擊非重疊部分荡短,父控件響應(yīng)事件丐枉。也就是說,點擊綠色的view的上半部分(即不重疊的部分)掘托,紅色的view響應(yīng)事件瘦锹。
分析
:我們知道,系統(tǒng)尋找能夠處理事件的最合適的view有兩個標(biāo)準(zhǔn)闪盔。第一弯院,這個view能接收事件,第二泪掀,這個點在自己身上听绳。這兩個條件缺一不可。此處我們點擊的點是綠色的view的上部分异赫,這個點沒有在紅色的view的坐標(biāo)系上椅挣。 也就是當(dāng)事件傳遞給紅色的view時,紅色的view雖然能夠接收這個事件祝辣,但是點不在紅色的view的坐標(biāo)系上贴妻,所以紅色的view不是最合適的view,這個事件就不會交給紅色的view處理蝙斜。紅色的view作為父控件都沒有接收到這個事件名惩,當(dāng)然他的子控件綠色的view也肯定接收不到這個觸摸事件。
我起初嘗試重寫紅色的view的hitTest:withEvent:方法孕荠,返回紅色的view為最合適的view娩鹉。雖然可以解決問題,但是點擊其他任何地方稚伍,也是紅色的view響應(yīng)事件弯予。這個結(jié)果不是我想要的,我只希望點擊紅色和綠色的部分个曙,紅色能夠響應(yīng)事件锈嫩,至于點擊其他部分,我不需要紅色響應(yīng)事件,那么該怎么做呢呼寸?
隨后艳汽,我又重寫了綠色的view的hitTest:withEvent:方法,返回紅色的view对雪。但是河狐,點擊非重疊區(qū),沒響應(yīng)瑟捣。
最后的解決方法是這樣的:
#import "RedView.h"
@implementation RedView
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"redView %s",__func__);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 判斷被點擊的點在不在自己身上
// 把被點擊的點轉(zhuǎn)換成子控件坐標(biāo)系的點
CGPoint greenViewPoint = [self convertPoint:point toView:self.subviews[0]];
if ([self pointInside:point withEvent:event] || [self.subviews[0] pointInside:greenViewPoint withEvent:event]) { // 觸摸點在自己身上或者在子控件身上都返回自己作為最合適的view
return self;
}
return [super hitTest:point withEvent:event];
}
@end
需求情景五
當(dāng)子控件和父控件部分重疊時馋艺,點擊子控件,父控件和子控件都響應(yīng)事件迈套。也就是說捐祠,點擊綠色的view,不但綠色的view本身響應(yīng)事件交汤,紅色的view也響應(yīng)事件雏赦。
分析
:事件的響應(yīng)是順著響應(yīng)者鏈條向上傳遞的,即從子控件傳遞給父控件芙扎,touch方法默認(rèn)不處理事件星岗,而是把事件順著響應(yīng)者鏈條傳遞給上一個響應(yīng)者。這樣我們就可以依托這個原理戒洼,讓一個事件多個控件響應(yīng)俏橘。
#import "GreenView.h"
@implementation GreenView
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"greenView %s",__func__); // 重寫touch方法,自己可以響應(yīng)事件
[super touchesBegan:touches withEvent:event]; // 在調(diào)用系統(tǒng)默認(rèn)的方法圈浇,又把事件順著響應(yīng)者鏈條拋給上一個響應(yīng)者寥掐。這就做到了一個事件,多個控件響應(yīng)磷蜀。
}
@end
打印結(jié)果
:
這是一次點擊:
2016-02-27 21:23:37.503 事件處理和響應(yīng)[1020:60725] greenView -[GreenView touchesBegan:withEvent:]
2016-02-27 21:23:37.504 事件處理和響應(yīng)[1020:60725] redView -[RedView touchesBegan:withEvent:]
文/VV木公子(簡書作者)PS:如非特別說明召耘,所有文章均為原創(chuàng)作品,著作權(quán)歸作者所有褐隆,轉(zhuǎn)載轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)污它,并注明出處,所有打賞均歸本人所有庶弃!
如果您是iOS開發(fā)者衫贬,或者對本篇文章感興趣,請關(guān)注本人歇攻,后續(xù)會更新更多相關(guān)文章固惯!敬請期待!