按鈕可以啟動 App 的特定動作,可以客製化背景,也可以加入標題或圖示。這個系統針對大部分使用狀況,提供了一些預先設計好的按鈕樣式。你也可以完全客製自己的按鈕。
我相信我不需要再解釋按鈕的用途,這是一個非常基本的 UI 控制元件,你在所有的 App 中都會看到按鈕的蹤影。按鈕可以處理使用者的觸控動作,以觸發一些動作。倘若你之前有學過 iOS 程式設計的話,SwiftUI 的 Button
與 UIKit 的 UIButton
非常相似,不過前者更加彈性及可客製化,讀下去你就會了解我的意思了。我將在這篇文章中詳細介紹 SwiftUI 控件,而你會學到以下的技巧:
- 如何建立一個簡單的按鈕,並處理使用者的選擇
- 如何客製按鈕的背景、間距 (padding)、和字型
- 如何為按鈕添加邊框 (border)
- 如何建立一個有圖片和文字的按鈕
- 如何建立一個有漸層背景 (gradient background) 與陰影的按鈕
- 如何建立一個全寬度 (full-width) 的按鈕
- 如何建立一個可重複使用的按鈕樣式
- 如何加入一個好看的點擊動畫 (tap animation)
建立一個啟用 SwiftUI 的新專案
好的,讓我們從基礎開始,使用 SwiftUI 來建立一個簡單的按鈕,首先,開啟 Xcode,並使用 Single View Application 模板來建立一個新專案。輸入專案名稱,我將它設定為 SwiftUIButton ,不過你也可以改用其他名稱。最重要的是,請確認你有選取 Use SwiftUI 選項。
儲存專案後,Xcode 即會載入 ContentView.swift
檔,並於設計畫布中顯示預覽畫面。如果預覽畫面無法顯示,你可以點擊畫布中的 Resume 按鈕。
有了 SwiftUI,要建立按鈕非常簡單。基本上,你可以使用以下這段程式碼來建立按鈕:
Button(action: { // 所需執行的內容 }) { // 按鈕介面外觀設置 }
建立一個按鈕時,你需要提供這兩段程式碼:
- 所需執行的內容:使用者按下或選取按鈕後所執行的程式碼。
- 按鈕外觀設置:呈現按鈕介面外觀所需的程式碼。
舉例來說,如果你只想要將 Hello World 標籤 (label) 轉成一個按鈕,你可以將程式碼更新如下:
struct ContentView: View { var body: some View { Button(action: { print("Hello World tapped!") }) { Text("Hello World") } } }
現在如畫布所示,Hello World 文字就變成了可按的按鈕。
在設計畫布上的按鈕現在還無法按,如果要測試的話,你可以點選 Play 按鈕來執行這個 App。不過為了檢視按下 Hello World 後的訊息,你必須在 Play 按鈕上按右鍵,然後點選 Debug Preview。按下按鈕後,你就會在主控台 (console) 中看到訊息。
客製按鈕的字型與背景
你已經學會了如何建立一個簡單的按鈕,現在讓我們來看看,如何以內建修飾器 (modifier) 來客製按鈕的介面外觀。舉例來說,如果想改變背景與文字顏色,你可以像這樣使用 background
與 foregroundColor
修飾器:
Text("Hello World") .background(Color.purple) .foregroundColor(.white)
如果你要變更字型,你可以如此進一步使用 font
修飾器,並指定字型型式 (例如 .title
):
Text("Hello World") .background(Color.purple) .foregroundColor(.white) .font(.title)
變更完成之後,你的按鈕應該看起來像這樣:
如你所見,這個按鈕不怎麼好看,如果能夠在文字周圍加上些間距是不是更好呢?你可以這樣使用 padding
修飾器達到目的:
Text("Hello World") .padding() .background(Color.purple) .foregroundColor(.white) .font(.title)
變更完成後,畫布也會跟著更新按鈕。現在按鈕就好看多了。
修飾器順序的重要性
這邊我想要強調一件事,就是 padding
修飾器要置於 background
修飾器之前。如果你將程式碼改寫如下,結果將會完全不同。
如果你將 padding
修飾器置於 background
修飾器之後,你依然可以為按鈕加入間距,但是間距卻沒有背景顏色。如果你想知道原因,你可以像這樣來理解這些修飾器。
Text("Hello World") .background(Color.purple) // 1. 背景顏色變為紫色 .foregroundColor(.white) // 2. 設定前景/字型顏色為白色 .font(.title) // 3. 變更字型樣式 .padding() // 4. 以主顏色來加入間距(也就是白色)
反之,倘若 padding
修飾器置於 background
修飾器之前,按鈕就會這樣運作:
Text("Hello World") .padding() // 1. 加入間距 .background(Color.purple) // 2. 變更背景顏色為紫色,包括間距 .foregroundColor(.white) // 3. 設定前景/字型顏色為白色 .font(.title) // 4. 變更字型樣式
為按鈕添加外框
padding
修飾器並非一定要放置在最前面,排序都只是依照你的按鈕設計而定。譬如說,你想建立一個有邊框的按鈕:
你可以如此變更 Text
控件的程式碼:
Text("Hello World") .foregroundColor(.purple) .font(.title) .padding() .border(Color.purple, width: 5)
這裡我們設定前景顏色為紫色,並在文字周圍加入一些空的間距。border
修飾器是用來定義外框寬度與顏色的,你可以自行改變 width
參數,來看看效果為何。
再舉一個例子,譬如說,設計師提供了以下的按鈕設計,以目前所學的內容,你該如何實作它呢?在閱讀下一個段落之前,請自行花個幾分鐘來思考看看。
那麼,你所需要的程式碼就是這樣:
Text("Hello World") .fontWeight(.bold) .font(.title) .padding() .background(Color.purple) .foregroundColor(.white) .padding(10) .border(Color.purple, width: 5)
我們使用兩個 padding
修飾器來設計按鈕。第一個 padding
與background
修飾器搭配在一起,是用來建立以紫色作為背景搭配間距的按鈕。padding(10)
修飾器則為按鈕另外在周圍加上間距,而這個 border
修飾器則指定為將外框塗成紫色。
讓我們來看一些更複雜的範例。如果你想要設計如下具有圓角外框的按鈕,該如何做呢?
SwiftUI 內建名為 cornerRadius
修飾器,可以讓你很輕易地建立圓角。要以圓角來渲染 (render) 按鈕的背景,你只要使用這個修飾器,並如下指定邊角的直徑:
.cornerRadius(40)
要加上外框的圓角,需要多花一點功夫,因為這個 border
修飾器,無法讓你建立圓角。所以,我們需要畫出這個外框,並將其疊到按鈕上才能辦到。以下為最終的程式碼:
Text("Hello World") .fontWeight(.bold) .font(.title) .padding() .background(Color.purple) .cornerRadius(40) .foregroundColor(.white) .padding(10) .overlay( RoundedRectangle(cornerRadius: 40) .stroke(Color.purple, lineWidth: 5) )
這個 overlay
修飾器可以讓你疊加另外一個視圖在目前視圖的上方。在程式碼中,我們使用 RoundedRectangle
物件的 stroke
修飾器,來畫出一個圓角外框。這個 stroke
修飾器可以讓你設定框線的顏色與線寬。
建立圖片與文字型按鈕
到目前為止,我們只有設計文字按鈕。在真實的專案中,你或你的設計師可能想用圖片來設計按鈕。要建立圖片按鈕,除了使用 Image
控件來代替 Text
控件外,其程式碼語法是一樣的,如下所示:
Button(action: { print("Delete button tapped!") }) { Image(systemName: "trash") .font(.largeTitle) .foregroundColor(.red) }
為了方便起見,我們使用內建的圖示庫,也就是 SF Symbols(這裡我們使用垃圾桶圖示)來建立圖片按鈕。我們在 font
修飾器中指定使用 .largeTitle
,來讓圖片大一點。你的按鈕看起來應該像這樣:
同樣地,如果你想要建立一個圓形圖片按鈕,搭配純色的背景,你可以應用我們之前討論過的修飾器,如下圖所示。
你可以同時使用文字與圖片來建立按鈕,例如你想要將 “Delete” 放在圖示旁邊,可以將程式碼替代如下:
Button(action: { print("Delete tapped!") }) { HStack { Image(systemName: "trash") .font(.title) Text("Delete") .fontWeight(.semibold) .font(.title) } .padding() .foregroundColor(.white) .background(Color.red) .cornerRadius(40) }
這裏我們將圖片與文字控件以水平堆疊方式嵌入,這會將垃圾桶圖示與 Delete 文字並排。修飾器在 HStack
中設定了背景顏色、間距、與按鈕圓角,下圖就是呈現的結果。
按鈕加上漸層背景與陰影
有了 SwiftUI,要幫按鈕加上漸層背景非常容易。你不但可以在 background
修飾器指定顏色,更可以輕易地將漸層效果加到任何按鈕。你只要將這行程式碼:
.background(Color.red)
替代為:
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
SwiftUI 框架有幾個內建的漸層特效,上面的程式碼就應用了從左 (.leading
) 至右 (.trailing
) 的線性漸層。所以漸層的左側部分是紅色,慢慢地以藍色做結尾。
如果你想要讓漸層由上而下,你可以將程式碼更改如下:
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .top, endPoint: .bottom))
你可以採用自己喜好的顏色來渲染漸層效果。譬如說,你的設計師告訴你要採用這樣的漸層:
要將顏色從 hex (十六進位色碼) 轉換為 Swift 可以相容的格式,有好幾個方式,這裏我準備示範其中一種方式。在專案導覽器中,選取 asset 目錄 (也就是 Assets.xcassets
)。在空白區域 (AppIcon 下方) 按下右鍵, 並選取 New Color Set。
接下來,選取好顏色,並點擊 Show inspector 按鈕。然後點選屬性檢閱器 ( Attributes inspector) 圖示來打開顏色組的屬性。在名稱欄位,設定名稱為 DarkGreen。在 Color 區塊,變更輸入方法為 8-bit Hexadecimal。
現在你可以在 Hex 欄位設定顏色。以這個範例來說,輸入 #11998e
來定義顏色。重複這個步驟來定義另一個 #38ef7d
顏色組。你可以將這組顏色名稱命名為 LightGreen 。
現在你已經定義了兩個顏色組,讓我們回到 ContentView.swift
並更新程式碼。要使用這個顏色組,你只要將顏色名稱設定如下:
Color("DarkGreen") Color("LightGreen")
因此,要以 DarkGreen 與 LightGreen 顏色來渲染漸層,你只需要更新 background
修飾器如下:
.background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
如果你有修改正確的話,你的按鈕應該會有下圖的漸層背景:
本節還有一個要介紹的是修飾器:shadow
修飾器,它可以讓你在按鈕 (或任何視圖) 周圍畫出陰影。只要在 cornerRadius
修飾器之後加上這行程式碼,即可觀看結果:
.shadow(radius: 5.0)
此外,你可以控制陰影的顏色,半徑,與位置。以下為範例程式碼:
.shadow(color: .gray, radius: 20.0, x: 20, y: 10)
建立一個全寬度按鈕
大一點的按鈕通常可以吸引使用者的注意。有時候,你需要建立一個全寬度的按鈕,可以佔滿整個畫面的寬度。這個 frame
修飾器是設計來讓你控制視圖的尺寸。
不論你是想要建立一個固定大小的按鈕,或者是可變化寬度的按鈕,你都可以使用這個修飾器。要建立全寬度的按鈕,你可以變更 Button
程式碼如下:
Button(action: { print("Delete tapped!") }) { HStack { Image(systemName: "trash") .font(.title) Text("Delete") .fontWeight(.semibold) .font(.title) } .frame(minWidth: 0, maxWidth: .infinity) .padding() .foregroundColor(.white) .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(40) }
這裏的程式碼除了在 padding
之前加入了 frame
修飾器之外,跟我們剛所寫的非常相似,這裏我們定義一個按鈕的彈性寬度,我們設定 maxWidth
參數為 .infinity
。
這表示按鈕會填滿容器視圖 (container view) 的寬度。在畫布中,它現在應該可以顯示一個全寬度的按鈕了。
透過將 maxWidth
定義為 .infinity
,按鈕的寬度就會依照裝置的螢幕寬度來自動調整。如果你想要為這個按鈕加上一些水平方向的空間的話,可以在 .cornerRadius(40)
後插入一個 padding
修飾器:
.padding(.horizontal, 20)
以 ButtonStyle 來修飾按鈕
在真實的 App 中,同樣設計的按鈕會有好幾個功能。譬如說,你建立了三個按鈕 Delete、Edit 與 Share,而它們都有同樣的按鈕樣式如下:
你大致需要將程式碼撰寫如下 :
struct ContentView: View { var body: some View { VStack { Button(action: { print("Share tapped!") }) { HStack { Image(systemName: "square.and.arrow.up") .font(.title) Text("Share") .fontWeight(.semibold) .font(.title) } .frame(minWidth: 0, maxWidth: .infinity) .padding() .foregroundColor(.white) .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(40) .padding(.horizontal, 20) } Button(action: { print("Edit tapped!") }) { HStack { Image(systemName: "square.and.pencil") .font(.title) Text("Edit") .fontWeight(.semibold) .font(.title) } .frame(minWidth: 0, maxWidth: .infinity) .padding() .foregroundColor(.white) .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(40) .padding(.horizontal, 20) } Button(action: { print("Delete tapped!") }) { HStack { Image(systemName: "trash") .font(.title) Text("Delete") .fontWeight(.semibold) .font(.title) } .frame(minWidth: 0, maxWidth: .infinity) .padding() .foregroundColor(.white) .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(40) .padding(.horizontal, 20) } } } }
從以上的程式碼你可以見到,你需要為每一個按鈕複製所有修飾器。那如果你的設計師想要修改按鈕樣式呢?你就會需要修改所有修飾器!這會是一個非常繁鎖的工作,也不是一個好的程式碼寫法。那你該如何將樣式歸納在一起,然後重複使用呢?
SwiftUI 提供了一個稱作 ButtonStyle
的協定 (protocol),可以讓你建立自己的按鈕樣式。要為按鈕建立一個可以重複使用的樣式,我們可以建立一個新的 GradientBackgroundStyle
struct,讓它遵循 ButtonStyle
協定。在 #if DEBUG
上方插入以下這段程式碼:
struct GradientBackgroundStyle: ButtonStyle { func makeBody(configuration: Self.Configuration) -> some View { configuration.label .frame(minWidth: 0, maxWidth: .infinity) .padding() .foregroundColor(.white) .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(40) .padding(.horizontal, 20) } }
這個協定需要我們提供 makeBody
函數的實作,此函數可以傳入一個 configuration
參數。這裡的 configuration
參數包含了一個 label
屬性,讓你可以應用修飾器來變更按鈕的樣式。在以上的程式碼中,我們只需要應用前面使用過的同一組修飾器即可。
所以,你要如何針對按鈕客製樣式呢?SwiftUI 提供了一個稱作 .buttonStyle
的修飾器,讓你可以這樣設定按鈕樣式:
Button(action: { print("Delete tapped!") }) { HStack { Image(systemName: "trash") .font(.title) Text("Delete") .fontWeight(.semibold) .font(.title) } } .buttonStyle(GradientBackgroundStyle())
很酷吧?我們已經把程式碼簡化了,你只需要用一行程式碼,就可以輕鬆為所有按鈕設定樣式。
你也可以透過 configuration 的 isPressed
屬性,來判斷按鈕是否有被按下,這讓你可以在使用者按下按鈕時改變樣式。舉例來說,譬如我們想要製作一個當使用者按下去時會變小一點的按鈕,你可以加入一行程式碼如下:
這個 scaleEffect
修飾器讓你可以將按鈕 (或任何視圖) 放大或縮小。要放大按鈕,你可以傳遞一個大於 1.0 的值;要縮小按鈕,你就可以傳遞一個小於 1.0 的值。
.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
這行程式碼中所做的,就是當使用者按下按鈕時,按鈕就會縮小 (也就是 0.9
);而當使用者手指放開後,它會回復到原來的大小 (也就是 1.0
)。如果你執行這個 App,當按鈕縮放時,你應該會見到一個不錯的動畫效果。這就是 SwiftUI 的威力之處!你不需要再寫其他的程式碼,因為它已經內建了動畫特效。
原文:A Beginner’s Guide to SwiftUI Buttons