很多文章都講了關(guān)于事件響應(yīng)的話題讥电,但是我們是不是真正明白了事件是怎么尋找和怎么響應(yīng)的,還是這些文章僅僅在介紹以下兩個(gè)函數(shù)呢欣福?
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
當(dāng)然,這兩個(gè)函數(shù)也重要语泽,但是僅僅是其中的一部分
當(dāng)我們的手指點(diǎn)擊到屏幕上的時(shí)候贸典,一系列的操作開始了;
當(dāng)然踱卵,前半部分是操作系統(tǒng)做了很多工作瓤漏,涉及到硬件的相關(guān)操作,包括IOKit.framework 生成一個(gè) IOHIDEvent 事件颊埃,SpringBoard(屏幕管理)接收蔬充,然后進(jìn)行進(jìn)程的分發(fā),這些可以稍作了解班利,然后就是進(jìn)入到我們的程序中饥漫,我們的程序啟動(dòng)的時(shí)候,會(huì)注冊(cè)一個(gè)Source1罗标,通過port接收這些分發(fā)過來的事件庸队,收到觸發(fā)以后,回調(diào)這個(gè)函數(shù)闯割,__IOHIDEventSystemClientQueueCallback()彻消,在__IOHIDEventSystemClientQueueCallback()內(nèi)觸發(fā)的Source0,
Source0再觸發(fā)的 _UIApplicationHandleEventQueue()宙拉,到達(dá)事件隊(duì)列以后宾尚,IOHIDEvent在之前就被轉(zhuǎn)換成了Event事件,然后進(jìn)行事件的分發(fā)和響應(yīng)處理谢澈。
自己對(duì)事件的響應(yīng)過程做了區(qū)分煌贴,尋找響應(yīng)者,和具體觸發(fā)響應(yīng)兩個(gè)過程锥忿,這兩個(gè)過程中牛郑,涉及到一些細(xì)節(jié)需要注意。
尋找響應(yīng)者:
從當(dāng)前的window開始敬鬓,向外遍歷子視圖淹朋,尋找能響應(yīng)事件的view,這時(shí)候有一個(gè)點(diǎn)要注意钉答,在iOS系統(tǒng)中础芍,只有繼承于UIResponder的類,才能響應(yīng)事件希痴,這個(gè)類的內(nèi)部有touchesBegan 者甲、touchesEnded等四個(gè)方法春感,可以反向理解砌创,只有實(shí)現(xiàn)這幾個(gè)方法的類才能響應(yīng)事件虏缸,UIView、UIViewController嫩实、UIWindow都是繼承于這個(gè)類的刽辙。
尋找的過程不做細(xì)究,其他文章已經(jīng)寫得很好了甲献,通過一下兩個(gè)函數(shù):
-- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
來確定響應(yīng)者然后return
響應(yīng):
到了這一步宰缤,僅僅完成階段一,然后下一步才是真正的響應(yīng)過程晃洒,上邊提到慨灭,繼承于UIResponder的類才能響應(yīng)事件,但是在系統(tǒng)中球及,默認(rèn)能響應(yīng)事件的都是UIControl的子類氧骤,UIButton這些都是繼承于UIControl的。
這地方涉及到一個(gè)細(xì)節(jié)就是吃引,雖然繼承于UIResponder的類筹陵,都是實(shí)現(xiàn)了touchesBegan 、touchesEnded等四個(gè)方法镊尺,但是朦佩,他們內(nèi)部默認(rèn)的實(shí)現(xiàn)都是[self.nextResponder touchesBegin],也就是交個(gè)上一級(jí)的響應(yīng)者去響應(yīng)庐氮,只有UIControl是特例语稠,他們?cè)趖ouchesBegan實(shí)現(xiàn)了判斷響應(yīng)的過程,并且阻斷了事件的繼續(xù)向上傳遞弄砍,在UIControl的touchesBegan實(shí)現(xiàn)偽代碼如下
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
_touchInside = YES;
_tracking = [self beginTrackingWithTouch:touch withEvent:event];
self.highlighted = YES; //高亮設(shè)置
if (_tracking) {
UIControlEvents currentEvents = UIControlEventTouchDown;
if (touch.tapCount > 1) {
currentEvents |= UIControlEventTouchDownRepeat;
}
[self _sendActionsForControlEvents:currentEvents withEvent:event];
}
}
- (void)_sendActionsForControlEvents:(UIControlEvents)controlEvents withEvent:(UIEvent *)event
{
for (UIControlAction *controlAction in _registeredActions) {
if (controlAction.controlEvents & controlEvents) {
[self sendAction:controlAction.action to:controlAction.target forEvent:event];
}
}
}
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
[[UIApplication sharedApplication] sendAction:action to:target from:self forEvent:event];
}
根據(jù)這三個(gè)函數(shù)颅筋,就能了解,事件在UIControl中的響應(yīng)過程了输枯,并且是怎么被阻斷的(因?yàn)楦緵]有向后傳遞耙楸谩)。
這是sendAction函數(shù)內(nèi)部的偽代碼
if (target) {
typedef void(*EventActionMethod)(id, SEL, id, UIEvent *);
EventActionMethod method = (EventActionMethod)[target methodForSelector:action];
method(target, action, sender, event);
return YES;
}
直接就是函數(shù)指針的調(diào)用
注意:也不是到了UIControl就一定會(huì)響應(yīng)桃熄,你注冊(cè)button的時(shí)候先口,會(huì)設(shè)置點(diǎn)擊的type,單擊瞳收、雙擊碉京、長(zhǎng)按等,響應(yīng)的時(shí)候也會(huì)匹配過來的Event中的touch事件是不是和注冊(cè)的一致螟深,如果不一致谐宙,就不會(huì)響應(yīng)。
除了系統(tǒng)的這些UIControl還能怎么響應(yīng)系統(tǒng)的事件呢界弧,仿照UIControl去操作凡蜻,直接重寫touchesBegin方法搭综,在里邊進(jìn)行響應(yīng)的處理。
我們知道划栓,響應(yīng)時(shí)間除了UIControl這些兑巾,還有一類就是手勢(shì),手勢(shì)是單獨(dú)處理的忠荞,他的優(yōu)先級(jí)高于UIControl的蒋歌,那他的尋找響應(yīng)者的過程和響應(yīng)的過程又是怎么樣的呢?
當(dāng)尋找到能響應(yīng)事件的view以后委煤,不會(huì)馬上執(zhí)行touchesEnd方法堂油,而是查看有沒有手勢(shì)手勢(shì)事件,如果有手勢(shì)事件會(huì)碧绞,調(diào)用touchesCancel称诗,取消掉UIControl的touchEvent事件,去響應(yīng)手勢(shì)的點(diǎn)擊或者其他事件头遭。
注意:手勢(shì)的響應(yīng)不是立刻執(zhí)行的寓免,他的回調(diào)是在runloop睡眠之前執(zhí)行的,可以打印一下main runloop看一下计维,里邊專門有一個(gè)observer是干這個(gè)的袜香。
還有一個(gè)注意點(diǎn),就是UIControl在判斷響應(yīng)的時(shí)候鲫惶,是會(huì)拿出touch對(duì)象中的view比對(duì)是不是self蜈首,如果是才會(huì)響應(yīng)。
if ([touch view] == self) { //TODO: UIControlEvents中定義的事件的識(shí)別邏輯 [self beginTrackingWithTouch:touch withEvent:event]; }
也就是說欠母,你在button上欢策,蓋一層view。理論上來說赏淌,view會(huì)將響應(yīng)向上傳遞踩寇,但是,傳遞給button的時(shí)候六水,他發(fā)現(xiàn)俺孙,touch的view不是button,就不會(huì)響應(yīng)掷贾【﹂可能在開發(fā)中會(huì)遇到這個(gè)問題。
輔助理解想帅,很多人可能對(duì)touch對(duì)象的內(nèi)部不是很清楚场靴,下邊是UITouch的內(nèi)部實(shí)現(xiàn),通過這個(gè),就可以幫助理解怎么找到哪個(gè)window響應(yīng)旨剥、手勢(shì)咧欣、響應(yīng)的view相關(guān)。
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UITouch : NSObject
@property(nonatomic,readonly) NSTimeInterval timestamp;
@property(nonatomic,readonly) UITouchPhase phase;
@property(nonatomic,readonly) NSUInteger tapCount; // touch down within a certain point within a certain amount of time
@property(nonatomic,readonly) UITouchType type API_AVAILABLE(ios(9.0));
// majorRadius and majorRadiusTolerance are in points
// The majorRadius will be accurate +/- the majorRadiusTolerance
@property(nonatomic,readonly) CGFloat majorRadius API_AVAILABLE(ios(8.0));
@property(nonatomic,readonly) CGFloat majorRadiusTolerance API_AVAILABLE(ios(8.0));
@property(nullable,nonatomic,readonly,strong) UIWindow *window;
@property(nullable,nonatomic,readonly,strong) UIView *view;
@property(nullable,nonatomic,readonly,copy) NSArray <UIGestureRecognizer *> *gestureRecognizers