標籤:blog http io ar os 使用 sp for strong
當你設計App時你可能需要動態響應事件。例如,一個觸摸事件可能發生在螢幕上不同的對象中,你需要決定哪個對象來響應這個給定的事件,理解對象如何接收事件。
當使用者觸發的一個事件發生,UIKit會建立一個包含要處理的事件資訊的事件對象。然後她會將事件對象放入active app’s(應用程式物件,每個程式對應唯一一個)事件隊列。對於觸摸事件,事件對象就是UIevent對象封裝的一系列觸摸集合。對於動作事件,這個事件對象依賴於使用的framework和你關心哪種動作事件。
事件通過特殊的路徑傳遞直到被傳遞到一個可以處理該事件的對象。首先,單例的UIApplication對象從頂層的隊列中擷取事件,然後分發。典型的,它將事件發送到App的關鍵window(key window)對象,window則為了處理該事件而發送它到初始化對象(initial object),這個初始化對像依靠事件類型。
- 觸摸事件(Touch events)。對於觸摸事件,window對象首先會嘗試將事件傳遞給事件發生的view。這個view就是所謂的hit-test view。尋找hit-test view的方法叫 hit-testing,具體描述可見“Hit-Testing Returns the View Where a Touch Occurred.”。
- 動作事件和遠端控制事件(Motion and remote control events)。在這些事件中,window對象發送事件到第一個響應器。第一個響應器的描述見“The Responder Chain Is Made Up of Responder Objects.”。
事件傳遞路徑的最終目的時找出能處理和響應該事件的對象。因此,UIKit給適合處理該事件的對象發送事件。對於觸摸事件,這個對象就是hit-test view,對於其他事件,這個對象就是第一個響應器(first responder)。下面的章節解釋了hit-test view和first responder對象是如何被確定的。
Hit-Testing返回觸摸發生的view
iOS使用hit-testing尋找觸摸的view。 Hit-Testing通過檢查觸摸點是否在關聯的view邊界內,如果在,則遞迴地(recursively)檢查該view的所有子view。在層級上處於lowest(我理解就是離使用者最近的view)且邊界範圍包含觸摸點的view成為hit-test view。確定hit-test view後,它傳遞觸摸事件給該view。
舉例說明,假設使用者觸摸了圖中的view E。iOS通過如下順序尋找hit-test view。
- 觸摸點在view A中,所以要檢查子view B和C。
- 觸摸點不在view B中,但在C中,所以檢查C的子view D和E。
- 觸摸點不在D中,但在E中。
View E是這個層級上處於lowest的view的邊界範圍包含觸摸點,所以它成為了hit-test view。
hitTest:withEvent:方法通過傳遞進來CGPoint和UIEvent返回hit test view。該方法調用pointInside:withEvent:方法,如果傳入hitTest:withEvent:的point在view的邊界範圍內,則pointInside:withEvent:返回YES。然後,這個方法會在view的所有子view中遞迴的調用hitTest:withEvent:。
如果傳入hitTest:withEvent:的point在view的邊界範圍內,則pointInside:withEvent:返回NO。這個point會被忽略,hitTest:withEvent:返回nil。如果一個子view返回NO,則它所在的view的層級上的分支的子view都會被忽略。
Hit-test view是處理觸摸事件的第一選擇,如果hit-test view不能處理事件,該事件將從事件響應鏈中尋找響應器,直到系統找到一個處理事件的對象。具體見“The Responder Chain Is Made Up of Responder Objects”。
響應器鏈由響應器對象組成(The Responder Chain Is Made Up of Responder Objects)
一些類型的事件的傳遞依賴響應器鏈。響應器鏈(responder chain)是一系列相關的響應器對象。它開始於第一個響應器終止於應用對象(application object)。如果第一個responder不處理事件,則會根據responder chain將event傳遞給下一個responder。
Responder object,即可以響應和處理事件的對象。UIResponder類是所有responder對象的基類,它定義了動態介面,不僅處理事件也包括處理響應行為。包括UIApplication,UIViewController,和UIView類都是responder,這意味著所有view和大部分關鍵的controller對象都是responder。足以Core Animation layers不是responders。
First responder被設計來第一個接收事件。典型的,first responder是一個view object。之所以成為第一個responder由於兩個原因:
- 覆蓋canBecomeFirstResponder方法,返回YES。
- 接收becomeFirstResponder訊息。如果必須,一個object能發送給自身這個訊息。
。。。
響應器鏈遵照一個特殊的傳遞路徑(The Responder Chain Follows a Specific Delivery Path)
如果初始化對象(initial object)—— 即hit-test view或者first responder —— 不處理事件,UIKit會將事件傳遞給responder chain的下一個responder。每個responder決定它是傳遞事件還是通過nextResponder方法傳遞給它的下一個responder。這個操作繼續直到一個responder處理event或者沒有responder了。
Responder chain 序列在iOS確定一個事件並將它傳遞給initial object(通常是view)時開始。所以initial view有處理事件的第一個機會。描述了兩個不同的事件傳遞路徑(因為不同的app 設定)。一個App的事件傳遞路徑由app特殊的構成決定,但事件傳遞路徑會遵守相同的規則。
關鍵方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
返回在層級上離當前view最遠(離使用者最近)且包含指定的point的view。
關於hitTest方法的解釋見hitTest:withEvent:方法流程
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
返回boolean值指出receiver是否包含指定的point。
如下調用:手動指定當前view不響應事件
1234567 |
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { for (UIView *view in self.subviews) { if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) return YES; } return NO;}
|
總結:
事件的傳遞和響應分兩個鏈:
- 傳遞鏈:由系統向離使用者最近的view傳遞。UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
- 響應鏈:由離使用者最近的view向系統傳遞。initial view –> super view –> …..–> view controller –> window –> Application
http://nsdifficult.com/blog/20140314/event/
事件傳遞之響應鏈(Event Delivery: The Responder Chain)【轉】