在 storyboard 加入 xib 的 view

開發 iOS App 時,我們時常遇到在多個頁面重覆出現的元件,比方顯示下載中的 loading view。此時較好的做法是利用 xib 設計這些重覆出現的元件,如此我們只要設計一次,之後即可在多個地方使用。

不過我們常在 storyboard 設計 App 主要的畫面流程,有沒有方法直接在 storyboard 加入 xib 設計的畫面,而不用另外從程式加入呢 ?

有的,只要透過以下三個步驟即可實現。

  • 自訂繼承 UIView 的類別 A。
  • 在類別 A 的程式裡用 addSubview 加入 xib 設計的畫面。
  • 在 storyboard 的 controller 畫面加入 View 元件,將元件的 class 設成類別 A。

為什麼這樣就可以呢 ? 因為此時 View 元件的類別為 A,而類別 A 的程式將貼上 xib 設計的畫面,因此 App 執行時, 畫面在顯示類別 A 的元件時也會包含 xib 的畫面。

接下來就讓我們一步步在 storyboard 加入 xib 設計的彼得潘 loading view 吧。

1 在 LoadingView.xib 設計 loading view。

2 自訂繼承 UIView 的類別 LoadingView,然後將 xib 裡 View 元件的 class 設為 LoadingView。

class LoadingView: UIView

有了自訂的類別 LoadingView,之後即可將 xib 裡的元件拉 outlet 到 LoadingView,方便未來從程式控制。

接下來正式進入讓 storyboard 能加入 xib 畫面的關鍵步驟。

3 自訂繼承 UIView 的類別 LoadingSuperview。

class LoadingSuperview: UIView

我們將 class 命名為 LoadingSuperview,因為它將用 addSubview 加入 xib 設計的畫面,成為 LoadingView 元件的 superview。

4 在 LoadingSuperview 裡定義 function addXibView 加入 xib 設計的畫面。

func addXibView() {   if let loadingView = Bundle(for: LoadingView.self).loadNibNamed("\(LoadingView.self)", owner: nil, options: nil)?.first as? UIView {      addSubview(loadingView)      loadingView.frame = bounds   }}

說明

Bundle(for: LoadingView.self)

生成和 LoadingView 有關的 Bundle 物件。

if let loadingView = Bundle(for: LoadingView.self).loadNibNamed("\(LoadingView.self)", owner: nil, options: nil)?.first as? UIView

loadNibNamed 將生成 xib 裡的元件。由於 xib 裡可能有多個元件,因此它將回傳型別 [Any]? 的 array。

func loadNibNamed(_ name: String, 
owner: Any?,
options: [UINib.OptionsKey : Any]? = nil) -> [Any]?

LoadingView.xib 的內容如下圖,因此利用 array 的 first 即可取出我們想要的 Loading View 元件。

loadingView.frame = bounds

讓 loadingView 和之後在 storyboard 貼上的 LoadingSuperview 元件一樣大。由於當初 xib 裡 loadingView 預設的 Autoresizing 如下,因此之後 LoadingSuperview 大小改變時,loadingView 也會跟著縮放調整大小。

剛剛 loadingView.frame = bounds的方法搭配的技巧是 Autoresizing,比較習慣 Auto Layout 的朋友也可以在 addXibView 裡設定 Auto Layout 的條件。

5 在 LoadingSuperview 裡定義 function awakeFromNib。

awakeFromNib 將在 storyboard 裡的 LoadingSuperview 元件被載入時觸發,我們在 function 裡呼叫 addXibView 加入 xib 的 loading view。

override func awakeFromNib() {   super.awakeFromNib()   addXibView()}

6 在 storyboard 的多個畫面加入 View 元件,並將 View 的類別設為 LoadingSuperview。

是時候啟動 App 了 ! 如下圖所示,以下兩個畫面我們都可看到 xib 設計的彼得潘 loading view。

Jobs 常說 One more thing,我們也來跟他學習吧。One more thing,有沒有可能在 storyboard 看到 xib 設計的畫面,不用等 App 啟動呢 ?

當然可以呀。別忘了 UI 元件加上 @IBDesignable 後即可在 Interface Builder 顯示程式客製的內容。因此我們將 class LoadingSuperview 加上 @IBDesignable。

@IBDesignable class LoadingSuperview: UIView {

不過 LoadingSuperview 原本在 awakeFromNib 裡加入 xib,但 awakeFromNib 在 App 執行時才會呼叫。為了能在 storyboard 顯示時加入 xib,我們需要定義另一個 function,prepareForInterfaceBuilder

@IBDesignable class LoadingSuperview: UIView {   override func prepareForInterfaceBuilder() {      super.prepareForInterfaceBuilder()      addXibView()   }

prepareForInterfaceBuilder 的名字明白告訴我們,它是為了 Interface Builder 存在的 function,就像聖誕老公公是為了聖誕禮物存在一樣。此 function 並不影響 App 的執行,它只在 Interface Builder 顯示元件時執行,方便我們從程式指定元件在 Interface Builder 顯示的內容。

因此我們在裡面呼叫 addXibView,然後再切回 storyboard 的畫面。

Cool ! 即使 App 沒執行,storyboard 依然能顯示 xib 設計的彼得潘 loading view。

ps: 也許有人對 Bundle 的程式有疑問,好奇為何要用 Bundle(for: LoadingView.self),不用 Bundle.main 產生 Bundle ?

基本上用 Bundle.main 也可以,在 App 執行時可看到一樣的結果。不過 storyboard 顯示時會出問題,出現以下的錯誤訊息:

Main.storyboard: error: IB Designables: Failed to render and update auto layout status for UITableViewController

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com