讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 11.7 響應器鏈 >

11.7 響應器鏈

響應器是個知道如何直接接收UIEvent的對象(參見11.6節內容)。它之所以知道是因為它是UIResponder或UIResponder子類的實例。查看Cocoa類的繼承體系,你會發現與屏幕顯示相關的任何類都是個響應器。UIView是個響應器、UIWindow是個響應器、UIViewController是個響應器,甚至連UIApplication與應用委託也是個響應器。

UIResponder有4個底層方法來接收與觸摸相關的UIEvent:

·touchesBegan:withEvent:

·touchesMoved:withEvent:

·touchesEnded:withEvent:

·touchesCancelled:withEvent:

這些方法(touch方法)會被調用以通知某個觸摸事件的響應器。無論代碼最終以何種方式獲悉某個用戶相關的觸摸事件,實際上,即使代碼並不知曉某個觸摸事件(因為Cocoa以某種自動化的方式對觸摸進行響應而無需你的代碼介入),該觸摸最初也會通過這4個方法中的其中一個來告知響應器。

該通信機制首先會確定用戶觸摸了哪個響應器。當找到了正確的視圖後(單擊測試視圖),UIView的方法hitTest:withEvent:與pointInside:withEvent:就會得到調用。然後會調用UIApplication的sendEvent:方法,它又會調用UIWindow的sendEvent:,而它又會調用點擊測試視圖(響應器)正確的觸摸方法。

應用中的響應器會加入響應器鏈中,響應器鏈本質上會沿著視圖層次體系將響應器鏈接起來。一個UIView可能位於另一個UIView中(其父視圖)等,直到到達了應用的UIWindow(它沒有父視圖)。響應器鏈從下向上的樣子如下所示:

1.起始的UIView(這裡指的就是單擊測試視圖)。

2.如果該UIView是UIViewController的視圖,那麼就是該UIViewController。

3.UIView的父視圖。

4.重複第2步!不斷重複,直到到達......

5.UIWindow。

6.UIApplication。

7.UIApplication的委託。

11.7.1 推遲職責

我們可以使用響應器鏈推遲一個響應器對某個觸摸事件的處理。如果響應器接收到某個觸摸事件但卻不能對其進行處理,那麼該事件就會沿著響應器鏈向上查找能夠處理的響應器。這主要發生在如下兩種情況中:

·響應器沒有實現相關的觸摸方法。

·響應器實現了相關的觸摸方法,調用的是super。

比如,基本的UIView本身並沒有實現觸摸方法。這樣,在默認情況下,雖然UIView是個單擊測試視圖,但觸摸事件卻無法進入UIView中,它會沿著響應器鏈向上查找能夠對其響應的響應器。在某些情況下,將對這種觸摸的處理推遲到主背景視圖,甚至是控制它的UIViewController中是合情合理的。

下面要介紹的這個應用是我開發的。它是個簡單的拼圖遊戲:一個矩形圖片被劃分為多個小塊,並且被打亂了。用戶需要輕拍連續的兩個小塊來交換它們的位置。其背景視圖是個名為Board的UIView子類;每個小塊都是普通的UIView對象,並且是Board的子類。當用戶輕拍Board時,我們需要知道哪個塊會做出響應以及拼圖的總體佈局,不需要每一小塊包含任何輕拍檢測邏輯。因此,我利用響應器鏈來推遲職責:每一小塊都沒有實現任何觸摸方法,對每一小塊的輕拍會直接落到Board上,它會進行觸摸檢測並處理輕拍事件,並且告訴被輕拍的小塊應該做什麼。當然,用戶對此一無所知;從表面上看,你觸摸的是每一個小塊,並且由它作出了響應。

11.7.2  Nil-Targeted動作

所謂nil-targeted動作就是個目標-動作對,其中的目標為nil。由於並沒有指定目標動作,因此使用下面的規則:從單擊測試視圖(用戶與之交互的視圖)開始,Cocoa會沿著響應器鏈向上查找(一次查找一個響應器)能夠響應該動作消息的對象:

·如果找到了能夠處理該消息的響應器,那就會調用該響應器的方法,流程結束。

·如果一直到響應器鏈的頂部也找不到能夠處理該消息的響應器,那麼消息就不會得到處理(也沒有任何副作用),換言之,什麼都不會發生。

假設我們要通過代碼來配置一個按鈕,如下所示:


self.button.addTarget(nil,
    action: \"buttonPressed:\",
    forControlEvents: .TouchUpInside)
  

這是個nil-targeted動作。當用戶輕拍按鈕時會發生什麼事情呢?首先,Cocoa會查看UIButton本身,看看它能否響應buttonPressed:。如果不能,那麼它會查找其父視圖UIView,諸如此類,一直沿著響應器鏈向上查找。對於包含了按鈕的視圖來說,如果self是包含了這個視圖的視圖控制器,並且該視圖控制器所對應的類實現了buttonPressed:,那麼輕拍按鈕就會導致視圖控制器的buttonPressed:被調用!

通過代碼來構建nil-targeted動作是顯而易見的事情:創建一個目標-動作對,其中目標為nil,就像上一個示例那樣。不過,如何在nib中構建nil-targeted動作呢?答案就是:構造一個對First Responder代理對象的連接。這正是First Responder代理對象的作用!First Responder並不是某個已知類的真實對象,因此在將動作連接到它之前,你需要在First Responder代理對像中定義動作消息,如下所示:

1.選中nib中的First Responder代理,並切換至屬性查看器。

2.你會看到一個用戶定義的nil-targeted First Responder動作表格(可能為空)。單擊+按鈕,為新動作指定一個簽名;它必須接收一個參數(這樣其名字會以一個冒號結尾)。

3.現在可以按住Control鍵並將控件(如UIButton)拖曳到First Responder代理上使用指定的簽名來定義一個nil-targeted動作。