iOS總結-有關UITouch事件(三)之 UIResponder和響應者鏈條
參考: https://blog.csdn.net/zeng_zhiming/article/details/71747881
https://www.jianshu.com/p/44a5b59e7e85
https://www.jianshu.com/p/4ad8b71246f7
UIResponder
UIResponder類是專門用來響應使用者的操作處理各種事件的,觸控事件Touch Events 、運動事件Motion Events、遠端控制事件 Remote Control Events ,只有繼承了UIResponder才能響應事件, 如UIApplication / UIView/ UIViewController
事件傳遞和響應者鏈條
事件傳遞中UIWindow會根據不同的事件型別,用不同的方式尋找initial object.對於Touch Event,其實就是hit-testView,對於Motion/Remote Event. UIWindow會把例如震動/遠端控制的事件傳遞給當前的firstResponder.
事件的觸發流程:
IOKit.framework是系統核心的庫
SpringBoard.app相當於手機的桌面
Source1 是runloop響應自動喚醒的資源 source0 非主動喚醒的資源
Hit-Test的目的就是找到手指點選到的最外層的那個view,它進行類似於探測的工作,判斷是否點選在某個檢視上.
什麼時候Hit-Test
與Hit-Test相關的兩個方法:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
runLoop
發生觸控事件後,系統會將該事件加入到一個由UIApplication管理的事件佇列中,UIApplication會從事件佇列中取出最前面的事件並將其分發處理,通常,先發送時間給應用程式的主視窗UIWindow.UIWindow會調hitTest:withEvent:方法, 從後往前遍歷subviews陣列,找到點選的點在哪個subview,然後繼續呼叫subView的hitTest:withEvent:方法,直到在檢視繼承樹中找到一個最合適的子檢視來處理觸控事件,該子檢視即為hit-test view.
這個view和上面依附的手勢,都會和一個UITouch的物件關聯起來, 這個UITouch會作為事件傳遞的引數之一.我們可以看到UITouch裡面有個一個view和gestureRecognizers屬性,就是Hit-Test view和它的手勢.
hitTest:withEvent: 方法 如何找到最合適的控制元件來處理事件
1.判斷自己是否能接受觸控事件 ,能否與使用者互動
2.觸控點是否在自己身上? 呼叫pointInside:withEvent:
3.從後往前遍歷子控制元件陣列,重複前面的兩個步驟(從後往前: 按照addsubview的順序, 越晚新增的越先訪問)
4.如果沒有符合條件的子控制元件,那麼 就自己最適合處理
找到合適的檢視控制元件後, 就會呼叫檢視控制元件的touches方法來作具體的事件處理.
要攔截事件傳遞,可以使用pointInside:withEvent: 方法,在實現裡面直接return NO即可.那麼hitTest:withEvent:方法返回nil. 或者在hitTest:withEvent:直接return self, 不傳遞給子檢視.
可以用來擴大view的點選區域,如通過重寫button的子類的hitTest:withEvent:方法,判斷point在button的frame之外10pt內,就返回button自己.
時間響應
第一響應者是一個UIWindow物件接收到一個事件後, 第一個來響應的該事件的物件.
如果hit-test檢視不處理收到的事件訊息,UIKit則將事件轉發到響應者鏈中的下一個響應者,看其是否能對改訊息進行處理.
所有檢視按照樹狀層次結構組織,每個view都有自己的superView, 包括vc的self.view:
1.當一個view被新增到superView上的時候, 它的nextResponder就會被指向所在controller
2.當vc被初始化的時候,self.view的nextResponder會被指向所在的controller
如果當前這個view是控制器的self.view,那麼控制器就是上一個響應者,如果當前這個view不是控制器的view,那麼父控制元件就是上一個響應者)
3.vc的nextResponder會被指向self.view的superView
4.最頂級的vc的nextResponder指向UIWindow
5.UIWindow的nextResponder指向UIApplication
這就形成了響應鏈,通過UIResponder串連起來的
touches方法實際上沒做什麼,UIView繼承了它並重寫,把事件傳遞給nextResponder,相當於[self.nextResponder touchBegan:touches withEvent:event]. 當一個view沒有重寫touch事件,那麼這個事件就會一直傳遞下去, 直到UIApplication. 如果重寫了touch方法,這個view響應了事件,事件就被攔截了, 它的nextResponder不會收到這個事件.
響應鏈事件傳遞 向上傳遞:
1.如果view的控制器存在, 就傳遞給控制器,如果控制器不存在,則將其傳遞給它的父檢視.
2.在檢視層次結構的最頂級檢視,如果不能處理收到的事件/訊息,則將事件/訊息傳遞給window物件進行處理
3.如果window物件不處理,則將其事件/訊息傳遞給UIApplication物件
4.如果UIApplication不處理事件/訊息,則將其丟棄
監聽事件的基本流程:
1.當應用程式啟動以後建立UIApplication物件
2.然後啟動訊息迴圈監聽所有事件
3.當用戶觸控式螢幕幕的時候,訊息迴圈監聽到這個觸控事件
4.訊息迴圈首先把監聽到的觸控事件傳遞給UIApplication物件
5.UIApplication物件再傳遞給UIWindow物件
6.UIWindow物件再傳遞給UIWindow的根控制器rootViewController
7.控制器再傳遞給控制器所管理的view
8.控制器所管理的view在其內部搜尋看本次觸控的點在哪個控制元件的範圍內
9. 找到某個控制元件以後,呼叫這個控制元件的touchBegan方法,再一次向上返回,最終返回給訊息迴圈
10.訊息迴圈知道哪個按鈕被點選後, 在搜尋這個按鈕是否註冊了對應的事件,如果註冊了,就呼叫這個事件處理程式.(一般就是執行控制器中的事件處理方法)
手勢
手勢是被和觸控事件是兩個獨立的事,.
通過touches方法監聽view觸控事件, 缺點: 必須自定義view,由於是在view內部的touches方法中監聽觸控事件, 因此預設情況下,無法讓其他外界物件監聽view的觸控事件,不容易區分使用者的具體手勢行為.
Tap 敲擊 Press 長按 Swipe輕掃 Rotation 旋轉 Pinch 捏合 Pan 拖拽
UIControl型別的控制元件也是通過UIApplication物件進行轉發 呼叫方法是sendAction:to:forEvent: