讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 7.3 連接 >

7.3 連接

連接指的是nib文件中的操作。它聯合了兩個nib對象,從一個運行到另一個。連接有個方向:這也是我為什麼要用「從哪裡到哪裡」來描述它。這兩個對像叫作連接的源與目標。

有兩種類型的連接,插座變量連接與動作連接。本節後面的內容將會介紹它們、如何創建和配置,同時還會介紹它們所解決的問題的本質。

7.3.1 插座變量

nib加載並且其實例生成時會產生一個問題:如果沒有引用它們,那麼這些實例就是無用的。7.2節中,我們是通過捕獲nib加載時所實例化的頂層對像數組來解決這個問題的。不過還有另外一種方式:使用插座變量。這種方式會複雜一些,它需要提前進行一些配置工作,而這項工作很容易出錯。不過這種方式也是更加常用的,特別在nib是自動加載的情況下更是如此。

插座變量連接屬於一類連接,它有個名字,名字實際上是個字符串。當nib加載時,一些奇妙的事情就會發生。源對象與目標對像不再僅僅是nib中的潛在對像;它們會成為真實存在的實例。插座變量的名字用於定位插座變量源對像中相同名字的實例屬性,而目標對像則會被賦給該屬性。現在,源對象就會有一個指向目標對象的引用!

比如,假設nib中有個Dog對像和一個Person對象,Dog有個master實例屬性。如果從nib中的Dog對像到Person對像創建一個插座變量,並且將其命名為"master",那麼當nib加載時,Dog實例與Person實例就會創建出來,並且Person實例會被賦給該Dog實例的master屬性(如圖7-9所示)。

圖7-9:插座變量是如何通過引用來指向nib所實例化的對象的

nib加載機制並不會神奇地創建出實例屬性。也就是說,如果源對像不具有某個屬性,那麼當其實例化後,它並不會自動擁有這個屬性。源對像所對應的類需要事先通過這個實例屬性進行定義。這樣,要想使用插座變量,我們需要在兩處進行準備工作:一是源對像所對應的類,二是nib。這有點棘手,Xcode會幫助你,但也有可能把事情搞亂。(本章後面將會對此進行詳細介紹。)

7.3.2  nib擁有者

要想讓插座變量捕獲到從nib中創建的實例引用,我們需要一個從nib外部的對象到nib內部的對象的一個插座變量。這看起來似乎是不可能的事情,但實際上是可以的。nib編輯器可以通過nib擁有者對像創建出這樣的插座變量。首先,介紹如何在nib編輯器中找到nib擁有者對像;接下來介紹它到底是什麼:

·在故事板場景中,nib擁有者是頂層的視圖控制器。它是文檔大綱中所列出場景中的第1個對象,也是場景停靠欄中所顯示的第1個對象。

·在.xib文件中,nib擁有者是個代理對象。它是文檔大綱或停靠欄中所顯示的第1個對象,並且作為File's Owner列在Placeholders下面。

nib編輯器中的nib擁有者對像表示nib加載時nib之外已經存在的實例。當nib加載時,nib加載機制並不會實例化該對像;它已經是個實例了。實際上,nib加載機制會用真正的、已經存在的實例代替nib擁有者對象,使用它來實現涉及nib擁有者的任何連接。

不過請等等!nib加載機制是如何知道該用哪個真正的、已經存在的實例來代替nib中的nib擁有者對象呢?這是因為在nib加載時,系統會通過兩種方式告知它:

·如果代碼通過調用loadNibNamed:owner:options:或instantiateWith-Owner:options:來加載nib,那麼你需要將擁有者對像作為owner:參數。

·如果視圖控制器實例自動加載nib來獲得主視圖,那麼視圖控制器實例會將自身作為擁有者對象。

比如,回到Dog對象與Person對象。假設nib中有個Person nib對象,但沒有Dog nib對象。Nib擁有者對象是個Dog。Dog有個master實例屬性。我們配置一個從Dog nib擁有者對像到Person對象的插座變量,叫作"master"。接下來加載nib,將現有的Dog實例作為擁有者。nib加載機制會匹配Dog nib擁有者對象與這個已經存在的實際的Dog實例,並將新實例化的Person實例作為該Dog實例的master(如圖7-10所示)。

回到Empty View,下面通過重新配置來說明這個機制。我們已經通過ViewController.swift的代碼加載了View nib。代碼會在ViewController實例中運行。因此,我們將該實例作為nib擁有者。這種配置有些乏味,不過請耐心一些,因為理解如何使用這種機制是非常重要的。下面就來看看具體步驟:

1.首先,ViewController需要一個實例屬性。在ViewController類聲明體的開頭,插入屬性聲明,如下所示:


class ViewController: UIViewController {
    @IBOutlet var coolview : UIView!
  

圖7-10:來自於nib擁有者對象的插座變量

你已經理解了var聲明的含義;我們聲明了一個名為coolview的實例屬性。它聲明為Optional,這是因為在ViewController實例創建時它才會擁有「真正的」值;在nib加載時它會持有該值。@IBOutlet屬性告訴Xcode允許我們在nib編輯器中創建插座變量。

圖7-11:創建插座變量

2.編輯View.xib。首先要確保nib擁有者對像作為一個ViewController實例。選中File's Owner代理對象並切換到身份查看器。在第一個文本框中(Custom Class下面),將Name值設為ViewController。在文本框外單擊並保存。

3.現在創建插座變量!在文檔大綱中,按住Control鍵並將File's Owner對像拖曳到View上;拖曳時有一根線會跟隨著鼠標,鬆開鼠標。這時會出現一個提示,列出了可以創建的所有可能的插座變量(如圖7-11所示)。其中有兩個,分別是coolview與view。單擊coolview(不是view!)。

4.最後,我們需要修改nib加載代碼。現在不需要捕獲實例化對象的頂層數組。現在要改變一下方式,我們會自己加載nib並將self作為擁有者。這樣會自動設置coolview實例屬性,因此我們就可以使用它了:


NSBundle.mainBundle.loadNibNamed("View", owner: self, options: nil)
self.view.addSubview(self.coolview)
  

構建並運行,一切如預期一樣!第1行會加載nib,並將coolview實例屬性設為從nib實例化的視圖。第2行會在界面上顯示self.coolview,因為self.coolview現在就是該視圖。

下面總結一下。預先的配置有些棘手,因為要在兩個地方進行配置,即代碼中和nib中:

·當nib加載時,如果一個類的實例是擁有者,那麼這個類中一定會有一個實例屬性(不僅要創建該屬性,還要將其標記為@IBOutlet)。

·在nib編輯器中,當nib加載時,如果一個類的實例是擁有者,那麼nib擁有者對象的類必須要設為這個類。

·在nib編輯器中,一定要創建插座變量,其名字與屬性名相同,並且從nib擁有者到某個nib對像(只有當另外兩項配置做好了才可以執行這個步驟)。

如果上面一切都做好了,那麼當nib加載時,如果使用正確的類擁有者,該擁有者的實例屬性就會被設為插座變量的目標。

Xcode 7中,當在nib中配置指向一個對象的插座變量時,文檔大綱中所列出的對象名不再是泛泛的名字(如「View」),它會顯示插座變量的名字(如「coolview」)。該名字只不過是個標籤而已,它對於插座變量的操作沒有任何影響,你可以在身份查看器中修改它。

7.3.3 自動配置nib

在某些情況下,擁有者類與nib的配置可以自動進行。既然已經瞭解了如何手工配置擁有者與nib,我們也可以理解這些自動化配置。

一個重要的示例是視圖控制器是如何獲取其主視圖的。視圖控制器有一個view屬性。實際的視圖通常來自於nib。這樣,當nib加載時,視圖控制器就需要充當擁有者的角色,還需要有一個從nib擁有者對像到該視圖的view插座變量。如果查看持有視圖控制器主視圖的實際nib,你就會發現這一點。

回到Empty Window項目。編輯Main.storyboard。它有一個場景,其nib擁有者對象是View Controller對象。在文檔大綱中選中View Controller,切換至身份查看器。它會顯示出nib擁有者對象的類實際上就是ViewController!

保持文檔大綱中View Controller為選中狀態,切換至連接查看器。它顯示出實際上有一個從View Controller到View對象的插座變量連接,這個插座變量叫作"view"!如果將鼠標懸浮在該插座變量連接上,那麼畫布中的View對象就會高亮顯示,幫助你進行識別。

這說明了視圖控制器是如何獲取到其主視圖的!當視圖控制器需要其主視圖時(因為視圖要顯示在界面上),view nib就會加載——視圖控制器會成為擁有者。這樣,視圖控制器的view屬性會被設為這裡所設計的視圖。接下來,視圖會顯示在界面上:它與上面的內容會出現在運行的應用上。

對於第6章的Truly Empty項目亦如此。編輯MyViewController.xib。nib擁有者對象是File's Owner代理對象。選中File's Owner對象,切換至身份查看器。它會顯示出nib擁有者對象的類實際上是MyViewController!切換至連接查看器,它會顯示出有一個連接到View對象的插座變量,名為"view"!

這說明了視圖控制器是如何獲得其主視圖的。在調用MyViewController(nibName:"MyViewController",bundle:nil)實例化視圖控制器時,我們會告訴它去哪裡尋找其nib。不過,nib本身已經正確配置了,因為在創建MyViewController類並勾選上「Also create XIB file」復選框時,Xcode會幫我們做這件事。視圖控制器加載nib時會將自身作為擁有者,插座變量即可生效:來自於nib文件的視圖會成為視圖控制器的view,並顯示在界面上。

7.3.4 誤配置的插座變量

創建插座變量並能正常使用涉及幾件事情。我敢保證你今後肯定會在這個地方栽跟頭,插座變量無法正常使用。別生氣,也別擔心;請準備好!每個人都會遇到這個事情。重要的是,要能識別出問題所在,這樣才能知道到底哪裡出錯了。接下來我們有意做錯一些事情,目的是看看哪些情況會導致插座變量配置不正確:

插座變量名與源類中的屬性名不匹配

從Empty Window示例開始。運行項目來證明一切都如我們所願。現在,在ViewController.swift中,將屬性名改為badview:


@IBOutlet var badview : UIView!
  

為了讓代碼編譯通過,你還需要在viewDidLoad中修改對該屬性的引用:


self.view.addSubview(self.badview)
  

代碼編譯通過。不過當運行時,應用會崩潰,控制台上轉出的消息是:「This class is not key value coding-compliant for the key coolview」。

從技術上來說,這條消息表示當nib加載時,nib中的插座變量名(依舊是coolview)與nib擁有者的屬性名不匹配,這是因為我們將該屬性名修改成了badview,這導致配置出現了問題。實際上,一切都是正確的,不過我們繞過了nib編輯器,從插座變量源的類中刪除了對應的實例屬性。當nib加載時,運行時無法匹配插座變量的名字與插座變量源(ViewController實例)中的任何屬性,應用因此崩潰。

還有一些情況也會導致這種誤配置。比如,你可以修改一些東西,導致nib擁有者成為錯誤類的實例:


NSBundle.mainBundle.loadNibNamed("View", owner: NSObject, options: nil)
  

我們將owner設為一個通用的NSObject實例。結果一樣:NSObject類沒有與插座變量名相同的屬性,這樣當nib加載時應用就會崩潰,提示擁有者並不是「鍵值編碼兼容的」。會導致同樣錯誤的另一個常見做法是在nib中將nib擁有者類設為錯誤的類。

nib中沒有插座變量

在ViewController.swift中,將之前示例對屬性名的引用由badview改回到coolview。運行項目來證明修改是正確的。現在來做一些破壞!編輯View.xib。選中File's Owner並切換至連接查看器,單擊第2個橢圓形左側的X來取消coolview插座變量的連接。運行項目,應用會崩潰,控制台上轉出的消息是:「Fatal error:unexpectedly found nil while unwrapping an Optional value」。

將插座變量從nib中刪除。當nib加載時,ViewController實例屬性coolview(其類型是個隱式、展開的Optional,它包裝了一個UIView,即UIView!)不會被設置。這樣,它會保持其初始值,即nil。接下來,將其放到界面中來使用隱式展開的Optional:


self.view.addSubview(self.coolview)
  

Swift會展開Optional,不過你無法展開nil,因此程序會崩潰。

沒有視圖插座變量

對於這種情況來說,你需要使用第6章的Truly Empty示例,該示例會從一個.xib文件中加載視圖控制器的主視圖;我無法使用.storyboard文件來說明問題,因為故事板編輯器不允許這麼做。在Truly Empty項目中,編輯MyViewController.xib文件。選中File's Owner對象並切換至連接查看器,取消view插座變量的連接。運行項目。程序啟動時會崩潰,控制台轉出的消息是:「Loaded the『MyViewController』nib but the viewoutlet was not set」。

控制台消息已經說明了情況。作為視圖控制器主視圖源的nib必須要有一個從視圖控制器(nib擁有者對像)到視圖的view插座變量。

7.3.5 刪除插座變量

一致性地刪除插座變量(也就是說,不會導致上面提及的那些問題)涉及要同時修改幾處地方,就像創建插座變量一樣。建議按照下面這個順序進行:

1.取消nib中插座變量的連接。

2.從代碼中刪除插座變量聲明。

3.嘗試編譯,讓編譯器捕獲其餘的問題。

比如,假設要從Empty Window項目中刪除coolview插座變量,遵循上面提到的3個步驟,做法如下所示:

1.取消nib中插座變量的連接。要想做到這一點,請編輯View.xib,選中源對像(File's Owner代理對像),然後在連接查看器中單擊X取消coolview插座變量的連接。

2.從代碼中刪除插座變量聲明。要想做到這一點,請編輯ViewController.swift,刪除或註釋掉@IBOutlet聲明這一行。

3.刪除對屬性的其他引用。最簡單的方式是構建項目;編譯器會對ViewController.swift中self.coolview這一行代碼報錯,因為現在已經沒有v屬性了。刪除或註釋掉該行,再次構建,驗證一切正常。

7.3.6 創建插座變量的其他方式

之前創建插座變量的方式是這樣的:首先在類文件中聲明一個實例屬性,然後在nib編輯器中,按住Control鍵,從文檔大綱的源(該類的實例)拖曳到目標處,從彈出列表中選擇所需的插座變量屬性。Xcode提供了多種方式來創建插座變量,這裡就不再一一列舉了。我會介紹一些常見的方式。

繼續使用Empty Window項目與View.xib文件。請記住,所有這些與對.storyboard文件的操作是一樣的。

通過連接查看器刪除View.xib中的插座變量(如果之前沒有做過)。在ViewController.swift中,創建(或取消註釋)屬性聲明,然後保存:


@IBOutlet var coolview : UIView!
  

現在來試一下!

從源連接查看器中拖曳

可以拖曳nib編輯器的連接查看器中的圓圈來連接插座變量。在View.xib中,選中File's Owner並切換至連接查看器。coolview插座變量會列出來,不過它尚未連接:右側的圓圈是打開的。從coolview旁邊的圓圈拖曳到nib中的UIView對像上。可以拖曳到畫布或文檔大綱中的視圖上。在拖曳圓圈時不需要按住Control鍵,也沒有提示列表,因為你是從特定的插座變量上拖曳的,Xcode知道是哪個。

從目標連接查看器中拖曳

現在按照相反的方向完成相同的步驟。刪除nib中的插座變量。選中View並打開連接查看器。我們需要一個將該視圖作為目標的插座變量:這是個「引用插座變量」。從New Referencing Outlet旁邊的圓圈拖曳到File's Owner對像上。提示列表會出現:單擊coolview來創建插座變量連接。

從源提示列表拖曳

可以使用與連接查看器相同的提示列表。下面就從這個提示列表開始。再一次,刪除連接查看器中的插座變量。按住Control鍵並單擊File's Owner。這時會彈出一個提示列表,看起來像是連接查看器!從coolview右側的圓圈拖曳到UIView上。

從目標提示列表拖曳

再一次,我們按照相反的方向完成相同的步驟。刪除連接查看器中的插座變量。在畫布或文檔大綱中,按住Control鍵並單擊視圖。這時會出現一個提示列表,顯示其連接查看器。從New Referencing Outlet旁邊的圓圈拖曳到File's Owner上。這時又會出現一個提示列表,列出了可能的插座變量;單擊coolview。

再一次,刪除插座變量。現在通過在代碼與nib編輯器間拖曳來創建插座變量。這要求你同時在兩處進行操作:你需要一個輔助窗格。在主編輯器窗格中,打開ViewController.swift。在輔助窗格中,打開View.xib,這樣視圖就是可編輯的了。

從屬性聲明拖曳到nib

代碼中,屬性聲明旁邊有個空心圓圈。你覺得它是幹什麼的呢?將其拖曳到nib編輯器的View上(如圖7-12所示)。這時,nib中會形成插座變量連接;可以通過連接查看器看到這一點,回到代碼,你會發現圓圈不再是空心的了。將鼠標懸浮在填充後的圓圈上或單擊它,看看它連接到了nib中的哪個插座變量上。單擊這個實心圓圈會彈出一個菜單,可以單擊菜單轉向目標對象。

圖7-12:從代碼拖曳到nib編輯器來連接插座變量

還有一種方式,也是最令人驚訝的方式。保留上一示例的兩個窗格排列。再一次,刪除插座變量(你可能需要使用連接查看器或nib編輯器中的彈出列表)。再從代碼中刪除@IBOutlet這一行!我們來創建屬性聲明並連接插座變量,一步即可搞定!

從nib拖曳到代碼

按住Control鍵將nib編輯器中的視圖拖曳到類ViewController的聲明體中。提示列表會顯示出Insert Outlet或Outlet Collection(如圖7-13所示)。鬆開鼠標。這時會出現一個彈出層,可以配置插入代碼中的聲明。按照圖7-14進行配置:你需要一個插座變量,該屬性應該命名為coolview。單擊Connect。這時,屬性聲明會插入代碼中,插座變量會在nib中進行連接,這一切只需一步操作即可。

直接連接代碼與nib編輯器來創建插座變量確實非常酷,也非常方便,不過要當心:並不存在這種直接的連接。要想讓插座變量能夠正常使用,總是要有兩部分內容:類中的實例屬性以及nib中的插座變量,其名字是相同的,來自於該類的實例。正是名字與類的同一性才使得nib加載時它們能在運行期匹配上。Xcode會幫助你將一切準備好,不過實際上並不是什麼魔法將代碼連接到nib上。

圖7-13:從nib編輯器拖曳到代碼來創建插座變量

圖7-14:配置屬性聲明

7.3.7 插座變量集合

插座變量集合指的是與相同類型對象的多個連接匹配(nib中)的數組實例屬性(代碼中)。

比如,假設一個類包含了如下屬性聲明:


@IBOutlet var coolviews: [UIView]!
  

結果就是在nib編輯器中,如果選中了該類的實例,那麼連接查看器就會在Outlet Collections而非Outlets下面列出coolviews。這意味著你可以構建多個coolviews插座變量,每個都會連接到nib中的不同UIView對像上。當nib加載時,這些UIView實例會成為數組coolviews的元素;插座變量構建的順序就是數組中元素的排列順序。

這麼做的好處在於代碼可以通過數字(數組索引)來引用從nib實例化的多個界面對象,而不必為每個對象使用不同的名字。在構建如自動佈局約束與手勢識別的插座變量時,這麼做是非常有用的。

7.3.8 動作連接

就像插座變量連接一樣,動作連接也是讓nib中的一個對像能夠引用另外一個對象的方式。不過,它並非屬性引用,而是消息發送引用。

所謂動作,就是當用戶對Cocoa UIControl界面對像(一個控件)執行了某種操作後由其產生並發送給其他對象的消息,如輕拍控件等。會導致控件發出動作消息的各種用戶行為叫作事件。要想查看完整的事件列表,請查看UIControl類的文檔,位於「Control Events」下。比如,對於UIButton,用戶輕拍按鈕對應於UIControlEvents.TouchUpInside事件。

控件對像要知道下面3點才能讓這個架構正常運作:

·響應什麼控件事件。

·當該控件事件(動作)發生時會發送什麼消息(調用的方法)。

·將消息發送給哪個對象(目標)。

nib中的動作連接會將這3點融入自身當中。它擁有作為源的控件對像;其終點就是目標;在構建時,你會指明動作連接,以及哪個控件事件與動作消息。為了構建動作連接,首先需要配置目標對像所對應的類,使之擁有適合於作為動作消息的方法。

為了嘗試動作連接,nib中需要有一個UIControl對象,如按鈕。Empty Window項目的Main.storyboard文件中可能已經有了這樣的按鈕。不過,當應用運行時,從View.xib中所加載的視圖可能會覆蓋這個按鈕。因此,首先需要在ViewController.swift中清除ViewController類聲明,使之不再有插座變量屬性與手工加載nib代碼;如下就是清理之後的代碼:


class ViewController: UIViewController {
}
  

下面將Empty Window項目中的視圖控制器作為按鈕UIControlEvents.TouchUpInside事件(表示輕拍了按鈕)所發出的動作消息的目標。我們需要在視圖控制器中添加一個方法,當輕拍按鈕後會調用該方法。為了看起來明顯一些,我們讓視圖控制器彈出一個警告窗口。將如下方法添加到ViewController.swift聲明體中:


class ViewController: UIViewController {
    @IBAction func buttonPressed(sender:AnyObject) {
        let alert = UIAlertController(
            title: "Howdy!", message: "You tapped me!", preferredStyle: .Alert)
        alert.addAction(
            UIAlertAction(title: "OK", style: .Cancel, handler: nil))
        self.presentViewController(alert, animated: true, completion: nil)
    }
}
  

@IBAction屬性就像是@IBOutlet一樣:它用來提示Xcode,讓Xcode在nib編輯器中加入這個方法。實際上,如果查看nib編輯器,你會發現它就在那兒:編輯Main.storyboard,選中View Controller對象並切換至連接查看器,你會看到buttonPressed:現在位於Received Actions下面。

在Main.storyboard中的唯一一個場景中,頂層View Controller的View應該會包含一個按鈕(本章之前創建的,如圖7-5所示)。如果不存在,請添加一個,然後將其放到視圖左上角。我們的目標是將按鈕的Touch Up Inside事件(作為動作)連接到ViewController中的buttonPressed:方法上。

與插座變量連接一樣,動作連接也有一個源和一個目標。這裡的源就是按鈕,目標是View Controller,View Controller實例作為包含按鈕的nib的擁有者。有多種方式可以構建這個插座變量連接,這與動作連接都是完全對應的。區別在於必須要配置連接的兩端。在按鈕(源)端,需要將Touch Up Inside指定為所要使用的控件事件;幸好,這是UIButton的默認值,因此可以省略這一步。在視圖控制器(目標)端,需要將buttonPressed:指定為要調用的動作方法。

下面按住Control鍵從按鈕拖曳到nib編輯器中的視圖控制器來構建動作連接:

1.按住Control鍵從按鈕(在畫布或文檔大綱中)拖曳到文檔大綱中所列出的View Controller(或拖曳到畫布中視圖上面的場景停靠欄中的視圖控制器圖標上)來創建連接。

2.這時會出現一個提示列表(如圖7-15所示),列出了可能的連接;它會列出Segue,以及Sent Event,特別是buttonPressed:。

3.單擊提示列表中的buttonPressed:。

現在會形成動作連接。這意味著當應用運行時,只要按鈕的Touch Up Inside事件觸發(表示按鈕被按下),那麼它就會向目標(視圖控制器實例)發送消息buttonPressed:。我們知道這個方法應該做什麼事情:它會彈出一個警告窗口。試一下吧!構建並運行應用,當應用出現在模擬器中時,單擊按鈕,事情與你想的一樣!

7.3.9 創建動作的其他方式

如果在ViewController.swift中創建了動作方法,那麼還有下面幾種方式可以在nib中創建動作連接:

圖7-15:展示出動作方法的提示列表

·按住Control鍵並單擊視圖控制器,這會彈出一個提示列表,類似於連接查看器。從buttonPressed:(位於Received Actions下)拖曳到按鈕。這會彈出另一個提示列表,列出可能的控件事件,單擊其中的Touch Up Inside。

·選中按鈕並使用連接查看器。從Touch Up Inside的圓圈拖曳到視圖控制器。這時會彈出一個提示列表,列出視圖控制器中已知的動作方法;單擊buttonPressed:。

·按住Control鍵並單擊按鈕。這時會彈出一個提示列表,類似於連接查看器。像之前一樣操作。

·在一個窗格中顯示ViewController.swift,在另一個窗格中顯示故事板。ViewController.swift中的buttonPressed:方法左側有一個圓圈。從該圓圈拖曳到nib中的按鈕上。

與插座變量連接一樣,創建動作連接最為直觀的方式就是從nib編輯器拖曳到代碼中,插入動作方法並在nib中構建動作連接,一步即可完成。首先請刪除代碼中的buttonPressed:方法以及nib中的動作連接。在一個窗格中顯示ViewController.swift,在另一個窗格中顯示故事板。現在:

1.按住Control鍵,從nib編輯器中的按鈕拖曳到ViewController類聲明體的空白區域。代碼中會彈出一個提示列表,提示創建插座變量或動作,鬆開鼠標。

2.一個彈出框會出現,這一塊比較棘手。在默認情況下,該彈出框用於創建插座變量連接,但這並非你想要的;你需要的是動作連接!將連接彈出菜單修改為Action。現在輸入動作方法的名字(buttonPressed)並配置聲明的其他部分(默認值就可以了,如圖7-16所示)。

Xcode會在nib中構建動作連接,並向代碼中插入一個樁方法:


@IBAction func buttonPressed(sender: AnyObject) {
}
  

圖7-16:配置動作方法聲明

這僅僅是個樁方法(Xcode肯定不知道這個方法要做什麼),在實際情況下,你需要在花括號之間插入一些功能代碼。就像插座變量連接一樣,動作方法代碼旁邊的實心圓圈表示Xcode已經認為連接就緒,可以單擊這個實心圓圈查看、導航到連接中的源對象。

7.3.10 誤配置的動作

與插座變量連接一樣,配置動作連接涉及兩端(nib與代碼)的一些處理與配置,這樣它們才能匹配上。因此,可以有意破壞動作連接的配置,並讓應用崩潰。通常的誤配置出現在嵌入nib動作連接中的動作方法名與代碼中的動作方法名不匹配。

為了證明這一點,請將代碼中的函數名由buttonPressed修改為其他名字,如buttonPushed。運行應用並輕拍按鈕。應用會崩潰並在控制台中顯示錯誤消息:「Unrecognized selector sent to instance」。選擇器是一條消息,即方法的名字。運行時嘗試向對像發送一條消息,不過該對象並沒有與之對應的方法(因為方法已經重命名了)。如果看一下錯誤消息開頭,你會發現它甚至告訴你了這個方法的名字:


-[Empty_Window.ViewController buttonPressed:]
  

運行時表示它嘗試調用Empty Window模塊ViewController類中的buttonPressed:方法,但ViewController類卻沒有這個方法。

7.3.11  nib之間的連接——不行!

不能在一個nib中的對象與另一個nib中的對象之間創建插座變量連接與動作連接。比如:

·不能在兩個不同的.xib文件中打開nib編輯器,然後按住Control鍵從一個文件拖曳到另一個文件來創建連接。

·在.storyboard文件中,不能按住Control鍵從一個場景中的對象拖曳到另一個場景中的對象來創建連接。

如果想這麼做,那就說明你還沒有理解nib(或場景、連接)到底是什麼。

原因很簡單:nib中的對象會在nib加載時成為實例,因此在nib中將其連接起來是合理的,因為我們知道當nib加載時會有哪些實例。兩個對象可以從nib中實例化,其中一個可能是代理對像(nib擁有者),不過它們必須位於同一個nib中,這樣當nib加載時,我們可以根據具體情況來配置實際實例之間的關係。

如果插座變量連接或動作連接是從一個nib中的對象到另一個nib中的對象建立的,那麼就沒有辦法知道真正連接的實例是什麼,因為它們是不同的nib,會在不同時刻加載。從一個nib生成的實例與從另一個nib生成的實例之間的通信問題是程序中實例之間通信方式的一種特殊情況,詳見第13章。