一些開發者不喜歡使用介面建構器 (Interface Builder) 來建構 App UI。一切都可以使用程式來撰寫,即使是 UI 也不例外。我個人比較喜歡混合 Storyboard 與程式來佈局 App。
不過如果要教導初學者使如何建構 App,介面建構器比較能夠無痛上手。使用介面建構器來設計 App UI 是非常直覺的,即使沒有任何 iOS 程式經驗的人也可以入門。最棒的是你不需要撰寫一行程式既可以客製化一個 UI 元件(例如,按鈕)。舉例來說,你可以在屬性檢閱器 (Attributes Inspector) 變更背景顏色或者字型大小。你可以很容易地將預設的按鈕透過屬性的客製化,將其外觀變得更加美觀。

這樣說好了,介面建構器還是有其限制,不是所有 UI 元件的屬性都可以做設定。譬如,你可以使用介面建構器來建造一個像這樣的按鈕嗎?

要自訂一個像這樣的按鈕,你還是需要寫一些程式,或者開發你自己的類別。這不會是一個很大的問題。不過,倘若你能夠在介面建構器中設計這樣的按鈕,且即時檢視結果是不是更好呢?
IBInspectable 與 IBDesignable 是兩個可以讓這樣的概念成真的關鍵字。另外,在本篇文章中,我將介紹這兩個屬性。並告訴你如何利用它們來自訂 UI 元件。
IBInspectable 與 IBDesignable 的介紹
簡單的說,IBInspectable 可以讓你在介面建構器中加入而外的功能,透過標示UIView
的一個類別屬性,如 IBInspectable 之後,此屬性接著便會在屬性檢閱器打開 (expose) 一個選項。另外,倘若你標示一個 UIview
類別像是 IBDesignable,介面建構器會即時渲染 (render) 整個自訂視圖。這表示當你編輯這些選項時,你可以見到這個自訂視圖的變更結果。
我們以下面這個例子,來進一步了解 IBInspectable 與 IBDesignable 。

你可能對如何製作一個圓角按鈕非常熟悉。倘若你不知道怎麼做,你可以修改 layer(層)的屬性來達成。每一個視圖物件的背後都是一個 CALayer
。要將按鈕改成圓角,你可以在程式中像這樣設定 layer 的 cornerRadius
屬性:
button.layer.cornerRadius = 5.0
button.layer.masksToBounds = true
設定為正值的話,便可以讓 layer 在它的背景畫出圓角。另外一個達到相同結果的方式是在識別檢閱器 (Identity inspector) 中設定「使用者定義運行屬性 (User Defined Runtime Attributes)」。

「使用者定義運行屬性」是介面建構器中非常強大的功能,可以讓你設置視圖的屬性。不過,它不是非常直覺。你必須記得視圖的每一個屬性,或者需要透過文件來查詢所需的屬性。
為了能夠讓視圖的客製化效果更佳, IBInspectable 在 Xcode 6 被導入。但這不並表示你不需要寫程式,寫程式還是必要的。不過你可在屬性檢閱器中取得更多功能的屬性設定。要建立一個圓角的按鈕,你可以像這樣宣告一個類別,並使用 @IBInspectable
來標註 cornerRadius
屬性:
class RoundedCornerButton: UIButton {
@IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
}
現在,當你在 Storyboard 中建立一個 RoundedCornerButton
物件,介面建構器會在屬性檢閱器增加另外一個稱作圓角半徑 (Corner Radius) 的選項,以及可以設定的值。

如果你仔細看一下這個選項的名稱,Xcode會自動將屬性名稱從 cornerRadius
轉換成 Corner Radius 。雖然這只是一個不怎麼起眼的功能,不過卻可以讓每一個選項都更容易辨識。
cornerRadius
屬性有一個 CGFloat
的型態。介面建構器顯示了 Corner Radius 選項並以數值來做調整。不是所有的屬性都可以夾在屬性檢閱器中,根據 Apple 的文件,IBInspector 會增加以下的型態:
- Int
- CGFloat
- Double
- String
- Bool
- CGPoint
- CGSize
- CGRect
- UIColor
- UIImage
倘若你宣告一個 IBInspectable 屬性,不過卻漏掉了支援的型態,介面建構器將不會在屬性檢閱器中為你產生這個選項。
雖然這個 @IBInspectable
關鍵字可以讓開發者公開任何的視圖屬性至介面建構器,你無法馬上看到調整後的結果,每次修改完圓角半徑值之後,你需要執行這個專案,才能夠見到按鈕在畫面的呈現情況。
IBDesignable 讓視圖客製化更進化,你現在可以將一個 UIView
類別以 @IBDesignable
關鍵字來做標註,如此介面建構器便會讓這個自訂的視圖能夠即時的更新。
@IBDesignable class RoundedCornerButton: UIButton {
@IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
}
使用如上面相同的例子(但是使用@IBDesignable
關鍵字),介面建構器可以依照屬性的變更來即時做顯示。

建立一個有設計感的按鈕
現在你應該對 IBInspectable 與 IBDesignable 有了基本的概念,我們來看如何在我們每日的工作中利用到它。底下的圖顯示一個標準的系統按鈕以及我們準備要建立,具有設計感的按鈕。

從 iOS 7 開始,系統的按鈕比較像是一個可以按的標籤,我們計畫透過屬性檢閱器來建立一個有設計感的按鈕,並且可以馬上在介面建構器中檢視到這些變化。我們可以從以下的客製化項目來製作出有設計感的按鈕:
- 圓角 (Corner radius)
- 邊框寬度 (Border width)
- 邊框顏色 (Border color)
- 標題距離左側、右側、上方與底邊邊距 (padding)
- 圖片距離左側、右側,上面與底部邊距
- 圖片左側/右側對齊 (alignment)
- 漸層顏色 (Gradient color)
我們來開始吧。首先,使用 Single View Application 模板來建立一個新專案,並將其命名為 FancyButton 。

專案建立之後,下載這個圖片包,並加上所有的圖示至素材目錄中。
好的,我們已經設定好專案,是時候來建立一個有設計感的按鈕。我們將為這個按鈕建立一個自訂類別。所以在專案導覽器中的 FancyButton 按下右鍵,並選取 New File… 。接著選擇 Cocoa Touch Class 模板。然後將這個新類別命名為 FancyButton ,並設定它的子類別為 UIButton
。
圓角半徑、邊框寬度與邊框顏色
我們從圓角半徑、邊框寬度與邊框顏色來開始。更新FancyButton
類別如下:
import UIKit
@IBDesignable
class FancyButton: UIButton {
@IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
@IBInspectable var borderWidth: CGFloat = 0.0 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable var borderColor: UIColor = .black {
didSet {
layer.borderColor = borderColor.cgColor
}
}
}
我們藉由@IBDesignable
這個關鍵字告訴介面建構器 FancyButton
要能夠即時更新,然後我們使用 IBInspectable 關鍵字來宣告三個屬性 (cornerRadius、 borderWidth 與 borderColor)。
現在開啟 Main.storyboard
來切換至介面建構器。我們將會加入一個按鈕來測試 FancyButton
類別。從元件庫拖曳一個按鈕元件至視圖控制器,並變更其標題為 SIGN IN (或者任意的標題名稱)。在識別檢閱器,將自訂類別 (Custom Class),從預設變更為 FancyButton
。

見證奇蹟的時刻到了!至屬性檢閱器,你會見到一個名稱為 Fancy Button 的區塊,其中有三個選項(包括 Corner Radius、Border Width、Border Color)。

你現在可以很容易建立一個如下圖所示的按鈕。倘若你建立一個相同的按鈕,將它調整為 343 x 50 點,設定它的圓角半徑為 4
,邊框寬度為 1
,以及邊框顏色設定為 red
。你可以測試其他的組合來觀察外觀的變化。

標題與圖片邊距
現在試著將控制元件的水平對齊,從置中改為靠左。我們來看會變成如何。

你可以從上圖見到,標題標籤與左側邊緣沒有間距。要如何將標題標籤加入邊距呢?UIButton
類別中有一個稱作 titleEdgeInsets
的屬性,可以用來調整標題標籤。你可以在四個不同的插入點(上面、左側、底邊、右側)中指定不同的值,設定為正值時會將標題靠近按鈕中間。現在修改 FancyButton
類別,並加入以下的 IBInspectable 屬性 :
@IBInspectable var titleLeftPadding: CGFloat = 0.0 {
didSet {
titleEdgeInsets.left = titleLeftPadding
}
}
@IBInspectable var titleRightPadding: CGFloat = 0.0 {
didSet {
titleEdgeInsets.right = titleRightPadding
}
}
@IBInspectable var titleTopPadding: CGFloat = 0.0 {
didSet {
titleEdgeInsets.top = titleTopPadding
}
}
@IBInspectable var titleBottomPadding: CGFloat = 0.0 {
didSet {
titleEdgeInsets.bottom = titleBottomPadding
}
}
我們加入四個新的屬性來讓開發者使用 FancyButton
,在介面建構器中設定標題標籤的邊距,你現在可以切換到 Main.storyboard
來做測試。

按鈕與圖片
UIButton
可以讓你將標題標籤以圖片來代替,你可以將標題設為 blank ,並變更圖片選項為 facebook
(也就是稍早前所導入的圖片),透過圓角半徑以及邊框選項的變更,你可以很容易地建立像這樣的按鈕:

透過按鈕元件的設置,你可以設定不同的值來變更按鈕的設計,譬如說,你想要建立一個加上邊框與圖片的圓形按鈕,你可以設定圓角的半徑為按鈕寬度的一半,並且設定圓角寬度為正值(譬如說 5)。下圖就是按鈕的範例介紹。

圖片邊距
在某些情況中,你想要將標題跟圖片加進一個按鈕中,譬如說,你想要建立一個 Sign in with Facebook 按鈕與 Facebook 圖示,你可以設定按鈕的標題為 SIGN IN WITH FACEBOOK,而圖片為 Facebook
,這張圖片會自動置於標題的左側。
順便說明一下,因為 Facebook 圖示為藍色,倘若你想要變更其顏色,你需要將按鈕的型態從 Custom 改為 System。這張圖片接著會被視為模板圖片,你可以透過 Tint 選項來變更其顏色。
預設上,Facebook 圖片與按鈕的左側沒有空間,還有,圖片與標題標籤間也沒有間距。你可以設定標題的左側邊距為 20
來加入間距,但是你要如何幫 Facebook 圖片加入邊距呢?

跟 titleEdgeInsets
類似,UIButton
有另外一個稱作 imageEdgeInsets
的屬性,可以讓你在圖片周圍加入邊距。現在打開 FancyButton.swift
,並在類別中插入以下的 IBInspectable
屬性:
@IBInspectable var imageLeftPadding: CGFloat = 0.0 {
didSet {
imageEdgeInsets.left = imageLeftPadding
}
}
@IBInspectable var imageRightPadding: CGFloat = 0.0 {
didSet {
imageEdgeInsets.right = imageRightPadding
}
}
@IBInspectable var imageTopPadding: CGFloat = 0.0 {
didSet {
imageEdgeInsets.top = imageTopPadding
}
}
@IBInspectable var imageBottomPadding: CGFloat = 0.0 {
didSet {
imageEdgeInsets.bottom = imageBottomPadding
}
}
完成變更之後,回到介面建構器,現在你只要透過設定 Image Left Padding 的值,就可以在圖片與按鈕的視圖的邊緣間加入邊距。

圖片與標題右側對齊
圖片預設是對齊按鈕標題的左側。那麼如果你想要將圖片對齊標題的右側,該怎麼修改呢?
其實有好幾種方式可以辦到,而我會採用 imageEdgeInsets.left
這個方式來完成。

參考一下上圖,要將按鈕的圖片視圖移到按鈕的右側邊緣,你可以設定 imageEdgeInsets.left
的值如下:
imageEdgeInsets.left = self.bounds.width - imageView.bounds.width
不過以上的算式並沒有將圖片視圖的右側邊距計算進去。

倘若我們想要將按鈕的圖片如上圖一樣對齊,我們必須將公式變更如下:
imageEdgeInsets.left = self.bounds.width - imageView.bounds.width - imageRightPadding
現在我們來進到實作部分,在 FancyButton
類別插入以下的程式:
@IBInspectable var enableImageRightAligned: Bool = false
override func layoutSubviews() {
super.layoutSubviews()
if enableImageRightAligned,
let imageView = imageView {
imageEdgeInsets.left = self.bounds.width - imageView.bounds.width - imageRightPadding
}
}
我們加入一個稱作 enableImageRightAligned
的屬性,來指示是否讓圖片靠右對齊。稍後,當你進到屬性檢閱器,你會見到一個可以讓你切換的 ON/OFF 開關。
因為我們依照按鈕的寬度(也就是 self.bounds.width)來計算左側邊距(也就是 imageEdgeInsets.left ),我們需要覆寫 layoutSubviews()
方法並更新其屬性。
變更完程式之後,切換回 Storyboard 並使用 FancyButton
來建立另一個按鈕,現在你可以設定 Enable Image Right Aligned 為 ON
以及將 Image Right Padding 設為 20
,如下所示。

顏色漸層
按鈕如果沒有漸層的話,稱不上有質感,對吧?所以最後一項功能,我們將實作的是為FancyButton
類別建立一個 IBInspectable 選項 。
那麼你該如何無痛且快速地建立一個漸層效果呢?
在 iOS SDK中,有一個稱作 CAGradientLayer
的類別,可以在其背景顏色上畫出漸層顏色。這是一個 CALayer
的子類別,只要像以下加入這幾行程式就可以讓開發者產生漸層顏色:
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.colors = [UIColor.blue, UIColor.red]
self.layer.insertSublayer(gradientLayer, at: 0)
CAGradientLayer
物件也各種設定不同漸層效果的屬性。不過,基本上你需要提供兩種顏色給 API 建立漸層顏色。在程式中,我們設定第一個顏色為 blue,而第二個顏色為 red。倘若你將這段程式放入 layoutSubviews()
方法中,你會見到以下的結果:

如你所見,預設漸層的方向是由上往下,倘若你想要變更為水平(譬如說由左至右)方向的漸層,你可以像這樣修改 startPoint
與 endPoint
屬性:
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
我已經介紹了漸層顏色的基礎,我們來修改 FancyButton
類別來支援漸層顏色,在 FancyButton
類別中加入三個新屬性,並更新 layoutSubview()
方法如下:
@IBInspectable var enableGradientBackground: Bool = false
@IBInspectable var gradientColor1: UIColor = UIColor.black
@IBInspectable var gradientColor2: UIColor = UIColor.white
override func layoutSubviews() {
super.layoutSubviews()
if enableImageRightAligned,
let imageView = imageView {
imageEdgeInsets.left = self.bounds.width - imageView.bounds.width - imageRightPadding
}
if enableGradientBackground {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.colors = [gradientColor1.cgColor, gradientColor2.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
這個 enableGradientBackground
屬性指示是否要將漸層效果應用在按鈕上,另外兩個屬性,可以讓你定義漸層顏色。
如果按鈕有採用漸層,我們建立 CAGradientLayer
物件,並以特定顏色來產生漸層效果。
現在你已經準備在介面建構器中測試,你可以在屬性檢閱器中啟用漸層功能,設定兩種顏色選項。不過,還有一件事要注意的是,介面建構器無法即時呈現漸層效果。

想要見到漸層效果,你必須要在模擬器中執行App,下圖所呈現的即為漸層效果。

總結
這個按鈕是不是很有質感且更酷呢?你現在有一個 FancyButton
類別,可以讓任何的 Xcode 專案來使用。假如你在一個團隊中,你也可以將這個類別分享給團隊的開發成員,他們也可以馬上在 Storyboard 中建立一個有質感的按鈕,且可以即時的檢視其變更的結果。
IBInspectable 與 IBDesignable 可以應用在大部分的視圖物件中,這裡提供一個作業,試著建立另一個客製化的物件,並讓開發者在介面建構器中設定它的屬性。
為了方便參考,您可以在這裡下載範例專案。