觸屏事件(Touch Event)
UIResponder
閱讀前請先了解一下以下2個方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
首先通過一個例子來了解一下UIResponder事件分發(fā)和響應(yīng)者鏈的過程(用戶點擊視圖View E)。
1.事件分發(fā)
(1)iOS系統(tǒng)檢測到手指觸摸(Touch)操作時會將其打包成一個UIEvent對象募闲,并放入當(dāng)前活動Application的事件隊列舶吗,單例的UIApplication會從事件隊列中取出觸摸事件并傳遞給單例的UIWindow來處理
(2)A是UIWindow的根視圖妻导,因此尤蒿,UIWindwo對象會首相對A進行hit-test假消,顯然用戶點擊的范圍是在A的范圍內(nèi)捷犹,因此红选,pointInside:withEvent:返回了YES澜公,這時會繼續(xù)檢查A的子視圖
(3)子視圖B點擊的范圍不再B內(nèi),因此B分支的pointInside:withEvent:返回NO喇肋,對應(yīng)的hitTest:withEvent:返回nil坟乾;子視圖C點擊的范圍在C內(nèi),即C的pointInside:withEvent:返回YES
(4)子視圖D點擊的范圍不再D內(nèi)蝶防,因此D的pointInside:withEvent:返回NO甚侣,對應(yīng)的hitTest:withEvent:返回nil;子視圖E點擊的范圍在E內(nèi)间学,即E的pointInside:withEvent:返回YES殷费,由于E沒有子視圖(也可以理解成對E的子視圖進行hit-test時返回了nil),因此低葫,E的hitTest:withEvent:會將E返回详羡,再往回回溯,就是C的hitTest:withEvent:返回E--->>A的hitTest:withEvent:返回E嘿悬。
至此实柠,本次點擊事件的第一響應(yīng)者就通過響應(yīng)者鏈的事件分發(fā)邏輯成功的找到了。
補充說明
hitTest:withEvent:方法將會忽略隱藏(hidden=YES)的視圖鹊漠,禁止用戶操作(userInteractionEnabled=YES)的視圖主到,以及alpha級別小于0.01(alpha<0.01)的視圖。如果一個子視圖的區(qū)域超過父視圖的bound區(qū)域(父視圖的clipsToBounds 屬性為NO躯概,這樣超過父視圖bound區(qū)域的子視圖內(nèi)容也會顯示)登钥,那么正常情況下對子視圖在父視圖之外區(qū)域的觸摸操作不會被識別,因為父視圖的pointInside:withEvent:方法會返回NO,這樣就不會繼續(xù)向下遍歷子視圖了。當(dāng)然娶靡,也可以重寫pointInside:withEvent:方法來處理這種情況牧牢。
2.響應(yīng)者鏈
當(dāng)發(fā)生事件響應(yīng)時,必須知道由誰來響應(yīng)事件姿锭。在 iOS 中塔鳍,由響應(yīng)者鏈來對事件進行響應(yīng)。
(1)所有事件響應(yīng)的類都是 UIResponder 的子類呻此,響應(yīng)者鏈?zhǔn)且粋€由不同對象組成的層次結(jié)構(gòu)轮纫,其中的每個對象將依次獲得響應(yīng)事件消息的機會。當(dāng)發(fā)生事件時焚鲜,事件首先被發(fā)送給第一響應(yīng)者掌唾,第一響應(yīng)者則是通過響應(yīng)者鏈的事件分發(fā)邏輯來確定的放前。
(2)如果第一響應(yīng)者無法響應(yīng)事件或者hit-test沒有找到第一響應(yīng)者,事件會隨著響應(yīng)鏈向上回溯糯彬,回溯順序如圖凭语,如果UIWindow實例和UIApplication實例都不能處理該事件,則該事件會被丟棄
(3)一般來說撩扒,第一響應(yīng)者是個視圖對象或者其子類對象似扔,當(dāng)其被觸摸后事件被交由它處理,如果它不處理搓谆,事件就會被傳遞給它的視圖控制器對象 ViewController(如果存在)炒辉,然后是它的父視圖(superview)對象(如果存在),以此類推泉手,直到頂層視圖辆脸。接下來會沿著頂層視圖(top view)到窗口(UIWindow 對象)再到程序(UIApplication 對象)。如果整個過程都沒有響應(yīng)這個事件螃诅,該事件就被丟棄。一般情況下状囱,在響應(yīng)者鏈中只要由對象處理事件术裸,事件就停止傳遞。
UIGestureRecognizer
假設(shè)如果沒有手勢亭枷,如果我們需要監(jiān)聽視圖的手勢則需要通過這四個方法
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event;
通過這些方法監(jiān)聽用戶手勢行為有明顯的缺點
(1)需要自定義視圖
(2)默認情況袭艺,外界不知道監(jiān)聽的觸摸事件情況
(3)不容易規(guī)范的區(qū)分具體手勢行為
因此蘋果推出了一系列的手勢行為,來簡化開發(fā)難度
現(xiàn)在來討論一下叨粘,UIGestureRecognizer和UIResponder的優(yōu)先級
如果在視圖A上添加一個Tap手勢猾编,此時點擊視圖E,則會發(fā)現(xiàn)并沒有響應(yīng)視圖E的action而是執(zhí)行了視圖A上Tap手勢的action
我們會發(fā)現(xiàn)手勢(2)的優(yōu)先級比所綁定的視圖(3)的優(yōu)先級高
到這里我們不妨再推敲一下,為什么要這樣設(shè)計這個機制呢?不能等view判斷自己能否處理之后再往下傳遞么?
答:如果是父view是縮放手勢,如果按照依次傳遞會怎么樣?可以看出在處理的時效性可準(zhǔn)確性方面不如這么設(shè)計好.
那蘋果的文檔在hit-test說的就是最上面的view處理不了再交給后面的view啊?這不矛盾么?
答:這不矛盾,我們看文檔不能斷章取義,不能太機械,蘋果在hit-test中說的是一種宏觀上的表現(xiàn)形式.
hit-test的目標(biāo)就是抓住touch對應(yīng)的響應(yīng)者鏈的頭,這樣我們就可以分發(fā)了,不然我們?nèi)绾胃咝シ职l(fā)呢?
參考資料
https://hit-alibaba.github.io/interview/iOS/Cocoa-Touch/Event-Handling.html
http://blog.csdn.net/zhoupengju/article/details/52250135