本文系轉(zhuǎn)載锌钮,原文地址為iOS觸摸事件全家桶
現(xiàn)在握爷,把膠卷回放到本章節(jié)開(kāi)頭的場(chǎng)景奴饮。給你一杯咖啡的時(shí)間看看能不能解釋得通那幾個(gè)現(xiàn)象了,不說(shuō)了泡咖啡去了...
我肥來(lái)了迈倍!
先看現(xiàn)象二伤靠,短按 cell無(wú)法響應(yīng),日志如下:
-[GLTableView touchesBegan:withEvent:]
backview taped
-[GLTableView touchesCancelled:withEvent:]
這個(gè)日志和上面離散型手勢(shì)Demo中打印的日志完全一致啼染。短按后宴合,BackView上的手勢(shì)識(shí)別器先接收到事件,之后事件傳遞給hit-tested view迹鹅,作為響應(yīng)者鏈中一員的GLTableView的 touchesBegan:withEvent:
被調(diào)用卦洽;而后手勢(shì)識(shí)別器成功識(shí)別了點(diǎn)擊事件,action執(zhí)行斜棚,同時(shí)通知Application取消響應(yīng)鏈中的事件響應(yīng)阀蒂,GLTableView的 touchesCancelled:withEvent:
被調(diào)用该窗。
因?yàn)槭录蝗∠耍虼薈ell無(wú)法響應(yīng)點(diǎn)擊蚤霞。
再看現(xiàn)象三(長(zhǎng)按Cell)酗失,長(zhǎng)按cell能夠響應(yīng),日志如下:
-[GLTableView touchesBegan:withEvent:]
-[GLTableView touchesEnded:withEvent:]
cell selected!
長(zhǎng)按的過(guò)程中昧绣,一開(kāi)始事件同樣被傳遞給手勢(shì)識(shí)別器和hit-tested view规肴,作為響應(yīng)鏈中一員的GLTableView的 touchesBegan:withEvent:
被調(diào)用;此后在長(zhǎng)按的過(guò)程中夜畴,手勢(shì)識(shí)別器一直在識(shí)別手勢(shì)奏纪,直到一定時(shí)間后手勢(shì)識(shí)別失敗,才將事件的響應(yīng)權(quán)完全交給響應(yīng)鏈斩启。當(dāng)觸摸結(jié)束的時(shí)候序调,GLTableView的 touchesEnded:withEvent:
被調(diào)用,同時(shí)Cell響應(yīng)了點(diǎn)擊兔簇。
OK发绢,現(xiàn)在回到現(xiàn)象一(快速點(diǎn)擊cell)。按照之前的分析垄琐,快速點(diǎn)擊cell边酒,講道理不管是表現(xiàn)還是日志都應(yīng)該和現(xiàn)象二一致才對(duì)。然而日志僅僅打印了手勢(shì)識(shí)別器的action執(zhí)行結(jié)果狸窘。分析一下原因:GLTableView的 touchesBegan
沒(méi)有調(diào)用墩朦,說(shuō)明事件沒(méi)有傳遞給hit-tested view。那只有一種可能翻擒,就是事件被某個(gè)手勢(shì)識(shí)別器攔截了氓涣。目前已知的手勢(shì)識(shí)別器攔截事件的方法,就是設(shè)置 delaysTouchesBegan
為YES陋气,在手勢(shì)識(shí)別器未識(shí)別完成的情況下不會(huì)將事件傳遞給hit-tested view劳吠。然后事實(shí)上并沒(méi)有進(jìn)行這樣的設(shè)置,那么問(wèn)題可能出在別的手勢(shì)識(shí)別器上巩趁。
Window的 sendEvent:
打個(gè)斷點(diǎn)查看event上的touch對(duì)象維護(hù)的手勢(shì)識(shí)別器數(shù)組:
捕獲可疑對(duì)象:UIScrollViewDelayedTouchesBeganGestureRecognizer
痒玩,光看名字就覺(jué)得這貨脫不了干系。從類名上猜測(cè)议慰,這個(gè)手勢(shì)識(shí)別器大概會(huì)延遲事件向響應(yīng)鏈的傳遞蠢古。github上找到了該私有類的頭文件:
@interface UIScrollViewDelayedTouchesBeganGestureRecognizer : UIGestureRecognizer {
UIView<UIScrollViewDelayedTouchesBeganGestureRecognizerClient> * _client;
struct CGPoint {
float x;
float y;
} _startSceneReferenceLocation;
UIDelayedAction * _touchDelay;
}
- (void).cxx_destruct;
- (id)_clientView;
- (void)_resetGestureRecognizer;
- (void)clearTimer;
- (void)dealloc;
- (void)sendDelayedTouches;
- (void)sendTouchesShouldBeginForDelayedTouches:(id)arg1;
- (void)sendTouchesShouldBeginForTouches:(id)arg1 withEvent:(id)arg2;
- (void)touchesBegan:(id)arg1 withEvent:(id)arg2;
- (void)touchesCancelled:(id)arg1 withEvent:(id)arg2;
- (void)touchesEnded:(id)arg1 withEvent:(id)arg2;
- (void)touchesMoved:(id)arg1 withEvent:(id)arg2;
@end
有一個(gè)_touchDelay變量,大概是用來(lái)控制延遲事件發(fā)送的别凹。另外草讶,方法列表里有個(gè) sendTouchesShouldBeginForDelayedTouches:
方法,聽(tīng)名字似乎是在一段時(shí)間延遲后向響應(yīng)鏈傳遞事件用的番川。為一探究竟到涂,我創(chuàng)建了一個(gè)類hook了這個(gè)方法:
//TouchEventHook.m
+ (void)load{
Class aClass = objc_getClass("UIScrollViewDelayedTouchesBeganGestureRecognizer");
SEL sel = @selector(hook_sendTouchesShouldBeginForDelayedTouches:);
Method method = class_getClassMethod([self class], sel);
class_addMethod(aClass, sel, class_getMethodImplementation([self class], sel), method_getTypeEncoding(method));
exchangeMethod(aClass, @selector(sendTouchesShouldBeginForDelayedTouches:), sel);
}
- (void)hook_sendTouchesShouldBeginForDelayedTouches:(id)arg1{
[self hook_sendTouchesShouldBeginForDelayedTouches:arg1];
}
void exchangeMethod(Class aClass, SEL oldSEL, SEL newSEL) {
Method oldMethod = class_getInstanceMethod(aClass, oldSEL);
Method newMethod = class_getInstanceMethod(aClass, newSEL);
method_exchangeImplementations(oldMethod, newMethod);
}
斷點(diǎn)看一下點(diǎn)擊cell后 hook_sendTouchesShouldBeginForDelayedTouches:
調(diào)用時(shí)的信息:
可以看到這個(gè)手勢(shì)識(shí)別器的 _touchDelay 變量中,保存了一個(gè)計(jì)時(shí)器颁督,以及一個(gè)長(zhǎng)得很像延遲時(shí)間間隔的變量m_delay〖模現(xiàn)在,可以推測(cè)該手勢(shì)識(shí)別器截?cái)嗔耸录⒀舆t0.15s才發(fā)送給hit-tested view沉御。為驗(yàn)證猜測(cè)屿讽,我分別在Window的 sendEvent:
,hook_sendTouchesShouldBeginForDelayedTouches:
以及TableView的 touchesBegan:
中打印時(shí)間戳吠裆,若猜測(cè)成立伐谈,則應(yīng)當(dāng)前兩者的調(diào)用時(shí)間相差0.15s左右,后兩者的調(diào)用時(shí)間很接近试疙。短按Cell后打印結(jié)果如下(不能快速點(diǎn)擊诵棵,否則還沒(méi)過(guò)延遲時(shí)間觸摸就結(jié)束了,無(wú)法驗(yàn)證猜測(cè)):
-[GLWindow sendEvent:]調(diào)用時(shí)間戳 :
525252194779.07ms
-[TouchEventHook hook_sendTouchesShouldBeginForDelayedTouches:]調(diào)用時(shí)間戳 :
525252194930.91ms
-[TouchEventHook hook_sendTouchesShouldBeginForDelayedTouches:]調(diào)用時(shí)間戳 :
525252194931.24ms
-[GLTableView touchesBegan:withEvent:]調(diào)用時(shí)間戳 :
525252194931.76ms
因?yàn)橛袃蓚€(gè) UIScrollViewDelayedTouchesBeganGestureRecognizer
祝旷,所以 hook_sendTouchesShouldBeginForDelayedTouches
調(diào)了兩次履澳,兩次的時(shí)間很接近』初耍可以看到距贷,結(jié)果完全符合猜測(cè)。
這樣就都解釋得通了吻谋。現(xiàn)象一由于點(diǎn)擊后忠蝗,UIScrollViewDelayedTouchesBeganGestureRecognizer
攔截了事件并延遲了0.15s發(fā)送。又因?yàn)辄c(diǎn)擊時(shí)間比0.15s短漓拾,在發(fā)送事件前觸摸就結(jié)束了阁最,因此事件沒(méi)有傳遞到hit-tested view,導(dǎo)致TableView的 touchBegin
沒(méi)有調(diào)用骇两。而現(xiàn)象二闽撤,由于短按的時(shí)間超過(guò)了0.15s,手勢(shì)識(shí)別器攔截了事件并經(jīng)過(guò)0.15s后脯颜,觸摸還未結(jié)束哟旗,于是將事件傳遞給了hit-tested view,使得TableView接收到了事件栋操。因此現(xiàn)象二的日志雖然和離散型手勢(shì)Demo中的日志一致闸餐,但實(shí)際上前者的hit-tested view是在觸摸后延遲了約0.15s左右才接收到觸摸事件的。
至于現(xiàn)象四 矾芙,你現(xiàn)在應(yīng)該已經(jīng)覺(jué)得理所當(dāng)然了才對(duì)舍沙。