iOS 的事件主要分為以下幾類:
Touch Events(觸摸事件)
Motion Events(運(yùn)動(dòng)事件,比如重力感應(yīng)蛤奥、搖一搖等)
Remote Events(遠(yuǎn)程事件 缅刽,比如用耳機(jī)上的事件來(lái)控制手機(jī))
事件傳遞中UIWindow會(huì)根據(jù)不同的event,用不同的方式尋找initial object,initial object決定于當(dāng)前的事件類型微姊。比如Touch Event配喳,UIWindow會(huì)首先試著把事件傳遞給事件發(fā)生的那個(gè)view,就是下文要說(shuō)的hit-testview涧团。對(duì)于Motion和Remote Event预厌,UIWindow會(huì)把例如震動(dòng)或者遠(yuǎn)程控制的事件傳遞給當(dāng)前的firstResponder。
注:以下只討論觸摸事件
響應(yīng)者鏈(Responder chain)
響應(yīng)者對(duì)象是指能夠?qū)τ脩艚换ナ录M(jìn)行響應(yīng)的對(duì)象鞠绰,響應(yīng)者鏈?zhǔn)怯梢幌盗许憫?yīng)者對(duì)象構(gòu)成順序鏈。整個(gè)app就通過(guò)nextResponder串成了一條鏈灶壶,也就是我們所說(shuō)的響應(yīng)鏈担扑。所以響應(yīng)鏈就是一條虛擬的鏈燕垃,并沒(méi)有一個(gè)對(duì)象來(lái)專門(mén)存儲(chǔ)這樣的一條鏈低矮,而是通過(guò)UIResponder的屬性串連起來(lái)的蝗锥。
事件的產(chǎn)生及傳遞
事件的產(chǎn)生
發(fā)生觸摸事件后穴张,系統(tǒng)會(huì)將該事件加入到一個(gè)由UIApplication管理的事件隊(duì)列中玻驻,為什么是隊(duì)列而不是棧嗤锉,是因?yàn)殛?duì)列是先進(jìn)先出,而棧是先進(jìn)后出,先產(chǎn)生的事件應(yīng)該先處理玫氢。
UIApplication會(huì)從事件隊(duì)列中取出最前面的事件牢屋,并將事件分發(fā)下去以便處理,通常事件先發(fā)送給應(yīng)用程序的主窗口(keyWindow)鼓择。
主窗口會(huì)在視圖層次結(jié)構(gòu)中找到一個(gè)最合適的視圖來(lái)處理觸摸事件夷野,這也是整個(gè)事件處理過(guò)程的第一步铸豁。找到合適的視圖控件后,就會(huì)調(diào)用視圖控件的touches方法來(lái)作具體的事件處理。
事件的傳遞
觸摸事件的傳遞是從父視圖傳遞到子視圖的咙轩,即UIApplication->Window->尋找處理事件最合適的view
如果父視圖不能接收觸摸事件,那么子視圖就不可能接收到觸摸事件软棺。
如何找到最合適的視圖(Hit-Testing View)來(lái)處理事件
1.首先判斷視圖是否可以接受觸摸事件
2.判斷觸摸點(diǎn)是否在視圖上
3.子視圖數(shù)組中從后往前遍歷喘落,重復(fù)前面兩個(gè)操作
4.如果沒(méi)有符合條件的子視圖赌朋,那么就認(rèn)為自己是最合適的事件處理者,也就是最合適的視圖
UIView不能接收觸摸事件的三種情況
不允許交互:userInterationEnable = NO
隱藏:如果把父視圖隱藏逾冬,那么子視圖也會(huì)隱藏,隱藏的視圖不能接收觸摸事件
透明度:如果設(shè)置一個(gè)視圖的透明度<=0.01,則該視圖不能接收觸摸事件
如何判斷觸摸點(diǎn)是否在視圖上
Hit-Test 機(jī)制
當(dāng)用戶觸摸(Touch)屏幕進(jìn)行交互時(shí),系統(tǒng)首先要找到響應(yīng)者(Responder)显设。系統(tǒng)檢測(cè)到手指觸摸(Touch)操作時(shí)僻焚,將Touch 以UIEvent的方式加入U(xiǎn)IApplication事件隊(duì)列中允悦。UIApplication從事件隊(duì)列中取出最新的觸摸事件進(jìn)行分發(fā)傳遞到UIWindow進(jìn)行處理。UIWindow 會(huì)通過(guò)hitTest:withEvent:方法尋找觸碰點(diǎn)所在的視圖溅呢,這個(gè)過(guò)程稱之為hit-test view澡屡。
那么什么是Hit-Test呢,我們可以把它理解為一個(gè)探測(cè)器咐旧,通過(guò)這個(gè)探測(cè)器我們可以找到并判斷手指是否點(diǎn)擊在某個(gè)視圖上面驶鹉,換句話說(shuō)就是通過(guò)Hit-Test可以找到手指點(diǎn)擊到的處于屏幕最前面的那個(gè)UIView。
在解釋Hit-Test是怎么工作之前铣墨,先來(lái)看看它是什么時(shí)候被調(diào)用的室埋。前面說(shuō)Hit-Test是一個(gè)探測(cè)器,那么在代碼里面其實(shí)就是一個(gè)函數(shù)伊约,UIView有如下兩個(gè)方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
每當(dāng)手指接觸屏幕姚淆,UIApplication接收到手指的事件之后,就會(huì)去調(diào)用UIWindow的hitTest:withEvent:屡律,看看當(dāng)前點(diǎn)擊的點(diǎn)是不是在window內(nèi)腌逢,如果是則繼續(xù)依次調(diào)用subView的hitTest:withEvent:方法,直到找到最后需要的view超埋。調(diào)用結(jié)束并且hit-test view確定之后搏讶,這個(gè)view和view上面依附的手勢(shì),都會(huì)和一個(gè)UITouch的對(duì)象關(guān)聯(lián)起來(lái)霍殴,這個(gè)UITouch會(huì)作為事件傳遞的參數(shù)之一媒惕,我們可以看到UITouch頭文件里面有一個(gè)view和gestureRecognizers的屬性,就是hitTest view和它的手勢(shì)来庭。
hitTest 的順序如下
UIApplication -> UIWindow -> Root View -> ··· -> subview
在視圖上調(diào)用pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi)妒蔚;
如果返回NO,那么hitTest:withEvent:返回nil月弛;
如果返回YES肴盏,那么它會(huì)從后往前遍歷子視圖數(shù)組執(zhí)行上述操作,直到有子視圖返回非空對(duì)象或者全部子視圖遍歷完畢尊搬。
如果有subview的hitTest:withEvent:返回非空對(duì)象叁鉴,則處理結(jié)束(注意這個(gè)過(guò)程,子視圖也是根據(jù)pointInside:withEvent:的返回值來(lái)確定是返回空還是當(dāng)前子視圖對(duì)象的佛寿。并且這個(gè)過(guò)程中如果子視圖的hidden=YES幌墓、userInteractionEnabled=NO或者alpha小于等于0.01都會(huì)并忽略);
如果所有subview遍歷結(jié)束仍然沒(méi)有返回非空對(duì)象冀泻,則hitTest:withEvent:返回self常侣;
系統(tǒng)就是這樣通過(guò)hit test找到觸碰到的視圖(Initial View)進(jìn)行響應(yīng)。
有了事件響應(yīng)鏈弹渔,接下來(lái)的事情就是尋找響應(yīng)事件的具體響應(yīng)者了胳施,我們稱著為:Hit-Testing View,尋找這個(gè)View的過(guò)程我們稱著為Hit-Test肢专。
響應(yīng)者鏈順序如下:
Initial View -> View Controller(如果存在) -> superview -> · ··? -> rootView -> UIWindow -> UIApplication
如果一個(gè)View有一個(gè)視圖控制器(View Controller)舞肆,它的下一個(gè)響應(yīng)者是這個(gè)視圖控制器焦辅,緊接著才是它的父視圖(Super View),如果一直到Root View都沒(méi)有處理這個(gè)事件椿胯,事件會(huì)傳遞到UIWindow(iOS中有一個(gè)單例Window)筷登,此時(shí)Window如果也沒(méi)有處理事件,便進(jìn)入U(xiǎn)IApplication哩盲,UIApplication是一個(gè)響應(yīng)者鏈的終點(diǎn)前方,它的下一個(gè)響應(yīng)者指向nil,以結(jié)束整個(gè)循環(huán)
事件傳遞的完整過(guò)程
先將事件對(duì)象由上往下傳遞(由父控件傳遞給子控件)廉油,找到最合適的控件來(lái)處理這個(gè)事件惠险。 調(diào)用最合適控件的touches….方法 如果調(diào)用了[super touches….];就會(huì)將事件順著響應(yīng)者鏈條往上傳遞,傳遞給上一個(gè)響應(yīng)者 接著就會(huì)調(diào)用上一個(gè)響應(yīng)者的touches….方法抒线。(如果視圖不能處理接收到的事件或者消息班巩,就會(huì)沿著響應(yīng)者鏈向上傳遞直到能夠響應(yīng)該事件的視圖,如果都不能響應(yīng)則舍棄)