一些開發者不喜歡使用介面建構器 (Interface Builder) 來建構 App UI。一切都可以使用程式來撰寫,即使是 UI 也不例外。我個人比較喜歡混合 Storyboard 與程式來佈局 App。
不過如果要教導初學者使如何建構 App,介面建構器比較能夠無痛上手。使用介面建構器來設計 App UI 是非常直覺的,即使沒有任何 iOS 程式經驗的人也可以入門。最棒的是你不需要撰寫一行程式既可以客製化一個 UI 元件(例如,按鈕)。舉例來說,你可以在屬性檢閱器 (Attributes Inspector) 變更背景顏色或者字型大小。你可以很容易地將預設的按鈕透過屬性的客製化,將其外觀變得更加美觀。
![Building-button-in-IB](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-1.png)
這樣說好了,介面建構器還是有其限制,不是所有 UI 元件的屬性都可以做設定。譬如,你可以使用介面建構器來建造一個像這樣的按鈕嗎?
![a-better-button](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-2-1024x109.png)
要自訂一個像這樣的按鈕,你還是需要寫一些程式,或者開發你自己的類別。這不會是一個很大的問題。不過,倘若你能夠在介面建構器中設計這樣的按鈕,且即時檢視結果是不是更好呢?
IBInspectable 與 IBDesignable 是兩個可以讓這樣的概念成真的關鍵字。另外,在本篇文章中,我將介紹這兩個屬性。並告訴你如何利用它們來自訂 UI 元件。
IBInspectable 與 IBDesignable 的介紹
簡單的說,IBInspectable 可以讓你在介面建構器中加入而外的功能,透過標示UIView
的一個類別屬性,如 IBInspectable 之後,此屬性接著便會在屬性檢閱器打開 (expose) 一個選項。另外,倘若你標示一個 UIview
類別像是 IBDesignable,介面建構器會即時渲染 (render) 整個自訂視圖。這表示當你編輯這些選項時,你可以見到這個自訂視圖的變更結果。
我們以下面這個例子,來進一步了解 IBInspectable 與 IBDesignable 。
![rounded-corner-button](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-3-1024x143.png)
你可能對如何製作一個圓角按鈕非常熟悉。倘若你不知道怎麼做,你可以修改 layer(層)的屬性來達成。每一個視圖物件的背後都是一個 CALayer
。要將按鈕改成圓角,你可以在程式中像這樣設定 layer 的 cornerRadius
屬性:
button.layer.cornerRadius = 5.0
button.layer.masksToBounds = true
設定為正值的話,便可以讓 layer 在它的背景畫出圓角。另外一個達到相同結果的方式是在識別檢閱器 (Identity inspector) 中設定「使用者定義運行屬性 (User Defined Runtime Attributes)」。
![User-Defined-Runtime-Attributes](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-4-1024x221.png)
「使用者定義運行屬性」是介面建構器中非常強大的功能,可以讓你設置視圖的屬性。不過,它不是非常直覺。你必須記得視圖的每一個屬性,或者需要透過文件來查詢所需的屬性。
為了能夠讓視圖的客製化效果更佳, 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) 的選項,以及可以設定的值。
![set-corner-radius](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-5-1024x201.png)
如果你仔細看一下這個選項的名稱,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
關鍵字),介面建構器可以依照屬性的變更來即時做顯示。
![IBDesignable-1](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-6-1024x184.png)
建立一個有設計感的按鈕
現在你應該對 IBInspectable 與 IBDesignable 有了基本的概念,我們來看如何在我們每日的工作中利用到它。底下的圖顯示一個標準的系統按鈕以及我們準備要建立,具有設計感的按鈕。
![button-demo-1](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-7-1024x398.png)
從 iOS 7 開始,系統的按鈕比較像是一個可以按的標籤,我們計畫透過屬性檢閱器來建立一個有設計感的按鈕,並且可以馬上在介面建構器中檢視到這些變化。我們可以從以下的客製化項目來製作出有設計感的按鈕:
- 圓角 (Corner radius)
- 邊框寬度 (Border width)
- 邊框顏色 (Border color)
- 標題距離左側、右側、上方與底邊邊距 (padding)
- 圖片距離左側、右側,上面與底部邊距
- 圖片左側/右側對齊 (alignment)
- 漸層顏色 (Gradient color)
我們來開始吧。首先,使用 Single View Application 模板來建立一個新專案,並將其命名為 FancyButton 。
![IBDesignable-2](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-8-1024x731.png)
專案建立之後,下載這個圖片包,並加上所有的圖示至素材目錄中。
好的,我們已經設定好專案,是時候來建立一個有設計感的按鈕。我們將為這個按鈕建立一個自訂類別。所以在專案導覽器中的 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
。
![IBDesignable-3](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-9-1024x185.png)
見證奇蹟的時刻到了!至屬性檢閱器,你會見到一個名稱為 Fancy Button 的區塊,其中有三個選項(包括 Corner Radius、Border Width、Border Color)。
![IBDesignable-4](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-10-1024x319.png)
你現在可以很容易建立一個如下圖所示的按鈕。倘若你建立一個相同的按鈕,將它調整為 343 x 50 點,設定它的圓角半徑為 4
,邊框寬度為 1
,以及邊框顏色設定為 red
。你可以測試其他的組合來觀察外觀的變化。
![IBDesignable-5](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-11-1024x123.png)
標題與圖片邊距
現在試著將控制元件的水平對齊,從置中改為靠左。我們來看會變成如何。
![IBDesignable-6](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-12-1024x221.png)
你可以從上圖見到,標題標籤與左側邊緣沒有間距。要如何將標題標籤加入邊距呢?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
來做測試。
![IBDesignable-7](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-13-1024x206.png)
按鈕與圖片
UIButton
可以讓你將標題標籤以圖片來代替,你可以將標題設為 blank ,並變更圖片選項為 facebook
(也就是稍早前所導入的圖片),透過圓角半徑以及邊框選項的變更,你可以很容易地建立像這樣的按鈕:
![IBDesignable-8](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-14-1024x387.png)
透過按鈕元件的設置,你可以設定不同的值來變更按鈕的設計,譬如說,你想要建立一個加上邊框與圖片的圓形按鈕,你可以設定圓角的半徑為按鈕寬度的一半,並且設定圓角寬度為正值(譬如說 5)。下圖就是按鈕的範例介紹。
![IBDesignable-9](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-15-1024x386.png)
圖片邊距
在某些情況中,你想要將標題跟圖片加進一個按鈕中,譬如說,你想要建立一個 Sign in with Facebook 按鈕與 Facebook 圖示,你可以設定按鈕的標題為 SIGN IN WITH FACEBOOK,而圖片為 Facebook
,這張圖片會自動置於標題的左側。
順便說明一下,因為 Facebook 圖示為藍色,倘若你想要變更其顏色,你需要將按鈕的型態從 Custom 改為 System。這張圖片接著會被視為模板圖片,你可以透過 Tint 選項來變更其顏色。
預設上,Facebook 圖片與按鈕的左側沒有空間,還有,圖片與標題標籤間也沒有間距。你可以設定標題的左側邊距為 20
來加入間距,但是你要如何幫 Facebook 圖片加入邊距呢?
![IBDesignable-10](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-16-1024x411.png)
跟 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 的值,就可以在圖片與按鈕的視圖的邊緣間加入邊距。
![IBDesignable-11](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-17-1024x400.png)
圖片與標題右側對齊
圖片預設是對齊按鈕標題的左側。那麼如果你想要將圖片對齊標題的右側,該怎麼修改呢?
其實有好幾種方式可以辦到,而我會採用 imageEdgeInsets.left
這個方式來完成。
![IBDesignable-12](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-18-1024x289.png)
參考一下上圖,要將按鈕的圖片視圖移到按鈕的右側邊緣,你可以設定 imageEdgeInsets.left
的值如下:
imageEdgeInsets.left = self.bounds.width - imageView.bounds.width
不過以上的算式並沒有將圖片視圖的右側邊距計算進去。
![IBDesignable-13](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-19-1024x318.png)
倘若我們想要將按鈕的圖片如上圖一樣對齊,我們必須將公式變更如下:
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
,如下所示。
![IBDesignable-14](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-20-1024x527.png)
顏色漸層
按鈕如果沒有漸層的話,稱不上有質感,對吧?所以最後一項功能,我們將實作的是為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()
方法中,你會見到以下的結果:
![button-demo-2](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-21-1024x158.png)
如你所見,預設漸層的方向是由上往下,倘若你想要變更為水平(譬如說由左至右)方向的漸層,你可以像這樣修改 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
物件,並以特定顏色來產生漸層效果。
現在你已經準備在介面建構器中測試,你可以在屬性檢閱器中啟用漸層功能,設定兩種顏色選項。不過,還有一件事要注意的是,介面建構器無法即時呈現漸層效果。
![IBDesignable-15](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-22-1024x510.png)
想要見到漸層效果,你必須要在模擬器中執行App,下圖所呈現的即為漸層效果。
![IBDesignable](https://www.appcoda.com.tw/content/images/2019/08/ibdesignable-23-1024x154.png)
總結
這個按鈕是不是很有質感且更酷呢?你現在有一個 FancyButton
類別,可以讓任何的 Xcode 專案來使用。假如你在一個團隊中,你也可以將這個類別分享給團隊的開發成員,他們也可以馬上在 Storyboard 中建立一個有質感的按鈕,且可以即時的檢視其變更的結果。
IBInspectable 與 IBDesignable 可以應用在大部分的視圖物件中,這裡提供一個作業,試著建立另一個客製化的物件,並讓開發者在介面建構器中設定它的屬性。
為了方便參考,您可以在這裡下載範例專案。