讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 7.2 nib加載 >

7.2 nib加載

nib文件是個關於潛在實例的集合,這些實例就是其nib對象。當應用運行並加載nib時,這些實例才會創建出來。這時,包含在nib中的nib對像會轉換為應用可以使用的實例。

這種架構頗具效率。nib通常會包含界面;界面是相對重量級的對象。nib只在需要時才會加載;實際上,它可能永遠都不會加載。通過這種方式,我們可以確保內存使用量最低,而這是非常重要的,因為移動設備上的內存是非常昂貴的資源。此外,加載nib是需要時間的,因此啟動時加載少量nib(足夠生成應用的初始界面即可)會加快啟動速度。

並沒有所謂的「卸載」nib。nib加載過程所完成的事情是生成一些實例;當這些實例生成後,nib的工作就完成了。此後將會由運行著的應用來決定該如何使用生成的這些實例。只要需要這些實例,應用就得保持對其的引用;如果不再需要,那就可以將其銷毀。

可以將nib文件看作用於生成實例的一組指令;當nib加載時會執行這些指令。相同的nib文件可以加載多次,每次都生成一組新的指令。比如,一個nib文件可能包含應用中多處都會用到的一些界面。代表表格中一行的nib文件可能會加載多次,從而生成表格中的多行。

7.2.1 何時加載nib

當應用運行時,有一些主要的場景通常會加載nib文件:

從故事板中實例化視圖控制器

故事板是場景的集合。每個場景都從一個視圖控制器開始。當需要該視圖控制器時,它會通過故事板實例化出來。這意味著包含該視圖控制器的nib會加載進來。

視圖控制器會通過故事板自動實例化出來。比如,當應用啟動時,如果有主故事板,那麼運行時就會尋找該故事板的初始化視圖控制器(入口點)並將其實例化(參見第6章)。與之類似,故事板常常會包含由Segue連接的幾個場景;在執行Segue時,目標場景的視圖控制器會被實例化出來。

還可以在代碼中以手工方式從故事板中實例化視圖控制器。要想做到這一點,請從一個UIStoryboard實例開始,然後:

·可以通過調用instantiateInitialViewController來實例化故事板的初始視圖控制器。

·可以通過調用instantiateViewControllerWithIdentifier:來實例化視圖控制器,前提是該視圖控制器的場景是在故事板中通過標識符字符串來命名的。

視圖控制器從nib中加載主視圖

視圖控制器有一個主視圖。不過視圖控制器是個輕量級對像(只包含少量代碼),而主視圖則是個相對重量級的對象。因此,在實例化時,視圖控制器會缺少主視圖。當需要將視圖放到界面中時,它才會將主視圖生成出來。視圖控制器可以通過幾種方式來包含主視圖;其中一種方式就是從nib中加載主視圖。

如果視圖控制器屬於故事板中的某個場景,並且在故事板的畫布中包含了視圖(通常來說均如此),就像Empty Window示例項目一樣,這就會涉及兩個nib:包含視圖控制器的nib與包含主視圖的nib。包含視圖控制器的nib會加載進來以便實例化視圖控制器,就像之前所介紹的那樣;現在,當該視圖控制器實例包含了主視圖時,主視圖nib會自動加載,連接到該視圖控制器的整個界面就會創建出來。

如果視圖控制器是通過其他方式實例化的,那就會有一個與之相關的.xibgenerated nib文件,其中包含了主視圖。重申一次,視圖控制器會自動加載這個nib,然後在需要時抽取出主視圖。這種視圖控制器與主視圖nib文件之間的關聯是通過nib文件名來實現的。第6章曾通過UIViewController初始化器init(nibName:bundle:)以代碼的方式配置這種關聯,如下所示:


self.window!.rootViewController =
    MyViewController(nibName:\"MyViewController\", bundle:nil)
  

上述代碼會讓視圖控制器將自己的nibName屬性設為\"MyViewController\"。這意味著當視圖控制器需要其視圖時,它是通過加載來自於MyViewController.xib的nib實現的。

代碼顯式加載nib文件

如果nib文件來自於.xib文件,那麼代碼可以手工加載它,這是通過調用如下方法之一來做到的:

loadNibNamed:owner:options:

這是個NSBundle實例方法。通常,你會直接調用NSBundle.mainBundle()。

instantiateWithOwner:options:

這是個UINib實例方法。在實例化UINib並通過init(nibName:bundle:)對其初始化時會確定好nib。

應用運行時指定nib文件實際上需要兩部分信息:其名字與包含它的包。視圖控制器不僅有nibName屬性,還有一個nibBundle屬性,用於指定nib的方法,如init(nibName:bundle:),會有一個bundle:參數。不過實際上,這個包都是應用包(或NSBundle.mainBundle(),它們是一回事);這是默認的,因此無須再指定包了。可以直接傳遞一個nil,不必再顯式提供一個包了。

7.2.2 手工加載nib

在實際情況下,你會將應用配置為會自動加載大多數nib,這與剛才提到的各種機制與場景相一致。不過為了理解nib加載過程,手工加載nib也是非常有益的,下面就來實現。

首先在Empty Window項目中創建並配置一個.xib文件:

1.在Empty Window項目中,選擇File→New→File並指定iOS→User Interface→View。這是個.xib文件,包含了一個UIView實例。單擊Next按鈕。

2.在Save對話框中,對新的.xib文件保持默認名字View,單擊Create按鈕。

3.現在回到項目導航器中;View.xib文件已經創建出來並被選中,可以在編輯器中查看其內容。這些內容包含了一個UIView。它非常大,因此請將其選中,在屬性查看器中,在Simulated Metric下,將Size彈出菜單修改為Freeform。這時,處理器會出現在畫布中的視圖旁邊;拖曳它將視圖縮小。

4.使用任意子視圖來裝配視圖,方式是將它們從對像庫中拖曳到視圖上。還可以配置視圖本身;比如,在屬性查看器中修改背景色(如圖7-7所示)。

圖7-7:在.xib文件中設計視圖

現在的目標是當應用運行時在代碼中手工加載這個nib文件。編輯ViewController.swift,在viewDidLoad方法體中,插入下面這行代碼:


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

構建並運行應用。發生了什麼?來自於View.xib的設計好的視圖去哪兒了?nib加載失敗了嗎?

不是。nib加載沒有失敗。它已經加載了!不過,我們還差兩步。記住,在加載nib時要執行3個任務。

1.加載nib。

2.加載時獲取它所創建的實例。

3.對實例進行一些處理。

我們執行了第1個任務(加載nib),但並沒有從中獲取實例。這樣,實例雖然創建出來,但隨後又煙消雲散了。為了防止這種情況的發生,我們需要通過某種方式捕獲到這些實例。對loadNibNamed:owner:options:的調用會返回一個頂層nib對像數組,這些nib對象是從nib加載過程中實例化的。這就是我們需要捕獲的實例!我們只有一個頂層nib對像(UIView),因此捕獲數組的第1個元素(也只有這唯一一個元素)即可。重寫代碼如下所示:


let arr = NSBundle.mainBundle.loadNibNamed(\"View\", owner: nil, options: nil)
let v = arr[0] as! UIView
  

現在執行了第2個任務:捕獲到加載nib時所創建的實例。現在,變量v會引用全新的UIView實例。

不過,在構建並運行應用時,依然什麼都不會出現,這是因為我們並未對該UIView進行任何處理。這是第3個任務。下面就對該UIView進行一些處理:將其放到界面上!再次重寫代碼,如下所示:


let arr = NSBundle.mainBundle.loadNibNamed(\"View\", owner: nil, options: nil)
let v = arr[0] as! UIView
self.view.addSubview(v)
  

構建並運行應用,視圖終於出現了!這證明了nib加載如我們所願:我們可以在運行著的應用界面上看到在nib中所設計的視圖(如圖7-8所示)。

圖7-8:nib加載的視圖出現在了界面上