前言
就iOS而言拓颓,app與用戶(hù)間的交互一般通過(guò)UIResponder中的touch類(lèi)方法讲坎,UIControl中的target action方法以及UIGestureRecognizer中的手勢(shì)來(lái)完成借跪。那么三者間的區(qū)別和聯(lián)系究竟是什么政己?
touch類(lèi)方法與target action比較
相信大家都知道,UIControl繼承自UIView掏愁,而UIView繼承自UIResponder歇由,即UIControl<--UIView<--UIResponder。所以果港,先分析touch類(lèi)方法與target action間的關(guān)系沦泌。
自定義TControl類(lèi),并實(shí)現(xiàn)touchesBegan方法
@interface TControl : UIControl
@end
@implementation TControl
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
[super touchesBegan:touches withEvent:event];
}
@end
在VC中這樣寫(xiě)
- (void)viewDidLoad {
[super viewDidLoad];
TControl *control = [[TControl alloc] initWithFrame:self.view.bounds];
[control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:control];
}
- (void)controlAction:(TControl *)control {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}
啟動(dòng)后點(diǎn)擊屏幕發(fā)現(xiàn)辛掠,先響應(yīng)touchesBegan后響應(yīng)controlAction谢谦。在controlAction中打上斷點(diǎn),再次點(diǎn)擊后可見(jiàn)調(diào)用棧如下:
可見(jiàn)公浪,target action的響應(yīng)依賴(lài)于UIControl中實(shí)現(xiàn)的touchesEnded他宛,而touchesEnded的響應(yīng)需要依賴(lài)于touchesBegan,可猜測(cè)欠气,如果將TControl類(lèi)touchesBegan中的super調(diào)用方法去掉,controlAction無(wú)法響應(yīng),即:
@implementation TControl
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}
@end
重復(fù)以上步驟可發(fā)現(xiàn)宜鸯,controlAction確實(shí)不會(huì)響應(yīng)憔古。
結(jié)論1:touch類(lèi)方法優(yōu)先于target action響應(yīng)淋袖,target action的響應(yīng)依賴(lài)于touch類(lèi)方法鸿市,因此也可通過(guò)touch類(lèi)方法實(shí)現(xiàn)阻斷target action的響應(yīng)焰情。
target action與手勢(shì)比較
添加手勢(shì)需要調(diào)用UIView中的addGestureRecognizer方法,而UIControl繼承自UIView(UIControl<--UIView)耕蝉,因此這兩者作比較。
如果代碼這樣寫(xiě):
@interface TControl : UIControl
@end
@implementation TControl
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
[super touchesBegan:touches withEvent:event];
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
TControl *control = [[TControl alloc] initWithFrame:self.view.bounds];
[control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchUpInside];
// [control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchDown];
[self.view addSubview:control];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
[control addGestureRecognizer:tap];
}
- (void)controlAction:(TControl *)control {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}
- (void)tapAction:(UITapGestureRecognizer *)tap {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
}
@end
會(huì)發(fā)現(xiàn)谦铃,無(wú)論TControl類(lèi)的touchesBegan中是否調(diào)用super师妙,tapAction始終響應(yīng),controlAction始終不響應(yīng)锯岖,并且響應(yīng)順序?yàn)橄萾ouch后gesture叫确。同樣在tapAction中打上斷點(diǎn)城看,再次點(diǎn)擊后調(diào)用棧如下:
可以看到,并沒(méi)有任何的touch類(lèi)方法被調(diào)用擎颖,這也說(shuō)明了為什么TControl中的touchesBegan即使沒(méi)調(diào)用super凹耙,手勢(shì)依然可以響應(yīng)。
同時(shí)肠仪,在UIGestureRecognizer文檔中可以找到如下描述:
A gesture recognizer doesn’t participate in the view’s responder chain.
即肖抱,手勢(shì)識(shí)別器不參與響應(yīng)鏈傳遞,由此可得:touch類(lèi)方法優(yōu)先gesture響應(yīng)异旧,但gesture響應(yīng)不依賴(lài)于touch類(lèi)方法意述。
那么是否說(shuō)明gesture的響應(yīng)優(yōu)先于target action?畢竟在TControl類(lèi)的touchesBegan中調(diào)用了super吮蛹,但是target action并沒(méi)有響應(yīng)荤崇。
繼續(xù)改造代碼:
@implementation TControl
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"[%@], %s", NSStringFromClass([self class]), __func__);
[super touchesCancelled:touches withEvent:event];
}
@end
運(yùn)行后點(diǎn)擊屏幕發(fā)現(xiàn),touchesCancelled被調(diào)用從而導(dǎo)致touchesEnded不會(huì)響應(yīng)潮针,上文分析過(guò)當(dāng)UIControl的UIControlEvents為UIControlEventTouchUpInside
時(shí)术荤,響應(yīng)action需要調(diào)用UIControl的touchesEnded方法才行。此時(shí)touchesEnded不響應(yīng)每篷,所以target action不響應(yīng)瓣戚。
同理端圈,在touchesCancelled打上斷點(diǎn)再次點(diǎn)擊屏幕后查看調(diào)用棧,可以發(fā)現(xiàn)的確是經(jīng)gesture傳遞后調(diào)用的touchesCancelled方法子库。
在UIGestureRecognizer.h中搜索cancel關(guān)鍵字舱权,會(huì)發(fā)現(xiàn)cancelsTouchesInView
這個(gè)BOOL屬性。屬性默認(rèn)值為YES仑嗅,當(dāng)手勢(shì)被識(shí)別后會(huì)調(diào)用手勢(shì)附加view的touchesCancelled方法宴倍,此時(shí)可能會(huì)導(dǎo)致部分事件無(wú)法響應(yīng)。當(dāng)屬性設(shè)置為NO時(shí)仓技,響應(yīng)鏈正常傳遞鸵贬。
tap.cancelsTouchesInView = NO;
當(dāng)加上這句代碼時(shí),可以發(fā)現(xiàn)tapAction和controlAction都是可以響應(yīng)的脖捻。
順便說(shuō)一下UIGestureRecognizer中另外兩個(gè)和touch相關(guān)的屬相delaysTouchesBegan
恭理、delaysTouchesEnded
。這兩個(gè)屬性也都是BOOL值郭变,其中delaysTouchesBegan默認(rèn)值為NO,delaysTouchesEnded默認(rèn)值為YES涯保。
上文通過(guò)分析得出過(guò)一個(gè)結(jié)論:touch類(lèi)方法優(yōu)先gesture響應(yīng)诉濒,但gesture響應(yīng)不依賴(lài)于touch類(lèi)方法。
其實(shí)并不完全正確夕春,這個(gè)結(jié)論的前提是delaysTouchesBegan == NO
未荒,如果設(shè)置為YES,touch類(lèi)的方法并不會(huì)調(diào)用及志。
tap.delaysTouchesBegan = YES;
結(jié)論2:當(dāng)gesture. delaysTouchesBegan == NO 時(shí)片排,touch類(lèi)方法優(yōu)先gesture響應(yīng)。當(dāng)gesture. delaysTouchesBegan == YES 時(shí)速侈,只響應(yīng)gesture方法率寡。同時(shí),gesture響應(yīng)不依賴(lài)于touch類(lèi)方法倚搬。
當(dāng)gesture. delaysTouchesBegan == YES
時(shí)冶共,在手勢(shì)識(shí)別的過(guò)程中不會(huì)響應(yīng)touch類(lèi)方法。在手勢(shì)識(shí)別失敗后每界,延遲調(diào)用當(dāng)前view的touchesBegan方法(值為NO時(shí)捅僵,識(shí)別失敗不延時(shí)調(diào)用,識(shí)別成功不調(diào)用)眨层。
同樣庙楚,當(dāng)gesture.delaysTouchesEnded == YES
時(shí),在手勢(shì)識(shí)別的過(guò)程中不會(huì)響應(yīng)touch類(lèi)方法趴樱。在手勢(shì)識(shí)別失敗后馒闷,延遲調(diào)用當(dāng)前view的touchesEnded方法(值為NO時(shí)酪捡,識(shí)別失敗不延時(shí)調(diào)用,識(shí)別成功不調(diào)用)窜司。
易錯(cuò)點(diǎn)
本文采用的是UIControl的UIControlEventTouchUpInside事件與UITapGestureRecognizer做對(duì)比沛善,細(xì)心的看官可以發(fā)現(xiàn),code中有一句注釋掉的代碼
// [control addTarget:self action:@selector(controlAction:) forControlEvents:UIControlEventTouchDown];
這里寫(xiě)的是UIControl的UIControlEventTouchDown事件塞祈,這個(gè)事件只要control的touchesBegan可以響應(yīng)就會(huì)調(diào)用金刁,不需要touchesEnded。所以具體事件具體分析议薪,理解原理后萬(wàn)變不離其宗尤蛮。