第 6 章
使用 SwiftUI 按鈕、標籤與漸層
按鈕可啟動App 的特定動作,其有自訂背景,並可加入一個標題或一個圖示。這個系統為大部分的使用情況提供許多預定義的按鈕樣式。你也可以充分設計自訂按鈕。
- Apple 的官方文件 (https://developer.apple.com/design/human-interface-guidelines/ios/controls/buttons/)
我想應該不需要去解釋什麼是按鈕,它是一個非常基本的 UI 控制元件,你可以在所有的 App 中看到它,按鈕可處理使用者的觸控動作,並觸發特定的動作。倘若你之前有學過 iOS 程式設計的話,SwiftUI 的 Button 與 UIKit 的UIButton 非常相似,但更具彈性與可客製化,待會你將會了解我的意思。在本章中,我將詳細介紹 SwiftUI 控制元件,你將學到下列的技巧:
- 如何建立一個簡單的按鈕,並處理使用者的選擇。
- 如何自訂按鈕的背景、間距與字型。
- 如何在按鈕上加入邊框。
- 如何建立包含圖片和文字的按鈕。
- 如何建立具有漸層背景與陰影的按鈕。
- 如何建立一個全寬度(full-width)的按鈕。
- 如何建立一個可重複使用的按鈕樣式。
- 如何加入一個點擊按鈕動畫。
啟用 SwiftUI 來建立新專案
好的,讓我們從基礎開始,使用 SwiftUI 來建立一個簡單的按鈕。首先開啟Xcode, 並使用「App」模板建立一個新專案。接著於下一個畫面輸入專案的名稱,我將它設定為「SwiftUIButton」,不過你可以自由使用其他的名稱。你只需確保於 Interface 選項下已選取「 SwiftUI」即可,如圖 6.1 所示。
當你儲存專案後,Xcode 應會載入 ContentView.swift
檔,並在設計畫布中顯示一個預覽畫面,如圖 6.2 所示。
使用 SwiftUI 建立按鈕非常簡單。基本上,你可以使用下列的程式碼片段來建立按鈕:
Button {
// 所需執行的內容
} label: {
// 按鈕的外觀設定
}
建立按鈕時,你需要提供這兩個程式碼區塊:
- 所需執行的內容 - 使用者點擊或選擇按鈕後執行的程式碼。
- 按鈕的外觀設定 - 描述外觀的程式碼區塊。
舉例而言,如果你只想要將「Hello World」標籤變成一個按鈕,則可以將程式碼更新如下:
struct ContentView: View {
var body: some View {
Button {
print("Hello World tapped!")
} label: {
Text("Hello World")
}
}
}
另外,其實你也可以將程式碼寫成這樣:
struct ContentView: View {
var body: some View {
Button(action: {
print("Hello World tapped!")
}) label: {
Text("Hello World")
}
}
}
兩段程式碼的作用完全相同,這只是編碼風格的問題。 在此書中,我們會使用第一種方法。
現在,你可以在畫布中看到「Hello World」文字變成可點擊的按鈕,如圖 6.3 所示。
print
語句會將訊息輸出到控制台(Console)。要對其測試的話,請點選「Play」按鈕來執行這個 App。如此當你點擊按鈕時,你將在主控台中看到該訊息,如圖 6.4 所示。如果你無法見到主控台,至 Xcode 選單,並選取 View > Debug Area > Activate Console 。
自訂按鈕的字型與背景
現在,你應該知道如何建立一個簡單的按鈕,我們來了解如何使用內建的修飾器來自訂其外觀。舉例而言,要改變背景與文字顏色,你可以使用 background
與 foreground Color
修飾器,如下所示:
Text("Hello World")
.background(Color.purple)
.foregroundColor(.white)
如果你要變更字型,則可進一步使用 font
修飾器,並指定字型樣式(例如:.title
):
Text("Hello World")
.background(Color.purple)
.foregroundColor(.white)
.font(.title)
變更完成後,你的按鈕應該看起來如圖 6.5 所示。
如你所見,這個按鈕看起來不怎麼好看,如果能在文字周圍加入一些間距不是會更好嗎?為此,你可以使用padding 修飾器,如下所示:
Text("Hello World")
.padding()
.background(Color.purple)
.foregroundColor(.white)
.font(.title)
變更完成後,畫布會據此更新按鈕。現在按鈕看起來好看多了,如圖 6.6 所示。
修飾器順序的重要性
這裡要強調的一件事,即「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
修飾器並非一定要始終放在最前面,而是取決於你的按鈕設計,例如:你要建立一個具有圖 6.8 所示的邊框的按鈕。
你可以如下更改 Text
控制元件的程式碼:
Text("Hello World")
.foregroundColor(.purple)
.font(.title)
.padding()
.border(Color.purple, width: 5)
這裡我們設定前景色為「紫色」,然後在文字周圍加入一些空白的間距。border
修飾器是用來定義邊框的寬度與顏色,請自行更改 width
參數的值來查看效果。
再舉一個例子,例如:一位設計師向你展示如圖 6.9 所示的按鈕設計,你將如何利用目前所學的內容來實作它呢?在閱讀到下一個段落之前,請自行花個幾分鐘來思考解決方案。
那麼,你所需要的程式碼如下:
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
修飾器則指定以紫色繪製邊框。
我們來看一個更複雜的範例。如果你想要設計如圖 6.10 所示、具有圓角外框的按鈕, 該如何做呢?
SwiftUI 內建了一個名為 cornerRadius
修飾器,可以讓你輕鬆建立圓角。要渲染圓角按鈕的背景,你只需使用這個修飾器並指定圓角即可,如下所示:
.cornerRadius(40)
要做出圓角邊框,需要多花一點工夫,因為這個 border
修飾器無法讓你建立圓角。因此,我們需要繪製邊框並將其疊在按鈕上。以下為最終的程式碼:
Text("Hello World")
.fontWeight(.bold)
.font(.title)
.padding()
.background(.purple)
.cornerRadius(40)
.foregroundColor(.white)
.padding(10)
.overlay {
RoundedRectangle(cornerRadius: 40)
.stroke(.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
,以使圖片大一點。你的按鈕看起來應如圖 6.11 所示。
同樣的,如果你想要建立一個具有純背景色的圓形圖片按,則可以應用我們之前討論過的修飾器圖 6.12 顯示了一個範例。
你可以同時使用文字與圖片來建立一個按鈕。例如:你想要將「Delete」文字放在圖示旁邊,則可將程式碼替換如下:
Button {
print("Delete button tapped")
} label: {
HStack {
Image(systemName: "trash")
.font(.title)
Text("Delete")
.fontWeight(.semibold)
.font(.title)
}
.padding()
.foregroundColor(.white)
.background(Color.red)
.cornerRadius(40)
}
這裡,我們將圖片與文字控制元件皆嵌入在一個水平堆疊中,這將使垃圾桶圖示與「Delete」文字並排。在HStack
中,修飾器設定了背景色、間距與按鈕的圓角,圖 6.13 顯示了最後結果的按鈕。
標籤的用法
SwiftUI 於 iOS 14 導入了一個新的視圖稱作 Label
,可以將圖片與文字並排,因此,你可以使用 Label
來替代 HStack
來實作同樣的佈局。
Button {
print("Delete button tapped")
} label: {
Label(
title: {
Text("Delete")
.fontWeight(.semibold)
.font(.title)
},
icon: {
Image(systemName: "trash")
.font(.title)
}
)
.padding()
.foregroundColor(.white)
.background(.red)
.cornerRadius(40)
}
建立具有漸層背景與陰影的按鈕
使用 SwiftUI,你便可輕鬆以漸層背景設計按鈕。你不僅可在 background
修飾器設定特定顏色,還可輕鬆為任何按鈕設定漸層效果,你只需將下列這行程式碼:
.background(.red)
替代為:
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
SwiftUI 框架有幾個內建的漸層效果,上列的程式碼從左( .leading
)至右( .trailing
)應用了線性漸層,其從左側的紅色開始,至右側的藍色結束,如圖 6.14 所示。
如果你想要由上而下應用漸層效果,則可以將程式碼更改如下:
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .top, endPoint: .bottom))
你可以自由使用自己喜好的顏色來渲染漸層效果。例如:你的設計師告訴你使用了如圖 6.15 所示的漸層。
有多種方式可以將色碼( color code )從十六進制( hex )轉換為Swift 相容的格式。這裡我將展示其中一種方式。在專案導覽器中,選取素材目錄(也就是 Assets
),然後在空白區域( AppIcon 下方)按右鍵,並選擇「New Color Set」,如圖 6.16 所示。
接下來, 於 「Any Appearance」選擇好顏色並點選「Show inspector」按鈕, 然後點選屬性檢閱器(Attributes Inspector )圖示來顯示顏色集的屬性。在「Name」欄位中,將名稱設定為「DarkGreen」,接著在「Color」區塊中,更改「Input Method」(輸入方法)欄位為「8-bit Hexadecimal」,如圖 6.17 所示。
現在,你可以在「Hex」欄位設定色碼。對於此範例,輸入 #11998e
來定義顏色並將顏色集命名為「LightGreen」。重複上述步驟,輸入#38ef7d
來定義另一個顏色集,你可以將顏色集命名為「DarkGreen」, 如圖 6.18 所示。
現在你已經定義了兩個顏色集,我們回到 ContentView.swift
並更新色碼。要使用顏色集,你只需使指定顏色集的名稱,如下所示:
Color("DarkGreen")
Color("LightGreen")
因此, 要使用「DarkGreen」與「LightGreen」顏色集來渲染漸層, 你只需要更新 background
修飾器,如下所示:
.background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
若是你已正確修改的話,則你的按鈕應該有很棒的漸層背景,如圖 6.19 所示。
在本小節中,我要介紹另一個修飾器。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
,這表示該按鈕將填滿容器視圖的寬度。在畫布中,它現在應該顯示了一個全寬度按鈕,如圖 6.20 所示。
透過將 maxWidth
定義為 .infinity
,按鈕寬度將根據裝置的螢幕寬度來自動調整。若是你想要給此按鈕更多的水平空間的話,則在 .cornerRadius(40)
後插入一個 padding
修飾器:
.padding(.horizontal, 20)
使用 ButtonStyle 設計按鈕
在真實世界的 App 中,相同的按鈕設計將運用於多個按鈕上,例如:你建立了Delete、Edit 與 Share 等三個按鈕,都具有相同的按鈕樣式,如圖 6.21 所示。
你可能撰寫下列的程式碼:
struct ContentView: View {
var body: some View {
VStack {
Button {
print("Share button tapped")
} label: {
Label(
title: {
Text("Share")
.fontWeight(.semibold)
.font(.title)
},
icon: {
Image(systemName: "square.and.arrow.up")
.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 {
print("Edit button tapped")
} label: {
Label(
title: {
Text("Edit")
.fontWeight(.semibold)
.font(.title)
},
icon: {
Image(systemName: "square.and.pencil")
.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 {
print("Delete button tapped")
} label: {
Label(
title: {
Text("Delete")
.fontWeight(.semibold)
.font(.title)
},
icon: {
Image(systemName: "trash")
.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
的協定,可以讓你建立自己的按鈕樣式。要為我們的按鈕建立一個可以重複使用的樣式, 則我們建立一個名為 Gradient BackgroundStyle
的新結構,該結構遵循 ButtonStyle 協定。在 struct ContentPreview_Previews
上方,插入下列這段程式碼:
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)
}
}
該協定要求我們提供接受 configuration
參數的 makeBody
函數的實作,configuration 參數包含一個 label
屬性,你可以應用修飾器來變更按鈕的樣式。在上列的程式碼中,我們只應用了之前所使用的同一個修飾器。
因此,你如何將自訂樣式應用於按鈕上呢? SwiftUI 提供了一個名為 .buttonStyle
的修飾器,讓你可以應用按鈕樣式,如下所示:
Button {
print("Delete button tapped")
} label: {
Label(
title: {
Text("Delete")
.fontWeight(.semibold)
.font(.title)
},
icon: {
Image(systemName: "trash")
.font(.title)
}
)
}
.buttonStyle(GradientBackgroundStyle())
很酷,是吧?程式碼現在已經被簡化了,你只要一行程式碼,即可輕鬆應用按鈕樣式於任何按鈕上。
你也可以透過 configuration 的 isPressed 屬性
,來判斷按鈕是否有被按下,這可以讓你在使用者按下它時改變按鈕的樣式。舉例而言,我們想要製作一個當使用者按下去時會變小一點的按鈕,你可以加入一行程式,如圖 6.23 所示。
scaleEffect
修飾器可以讓你縮放按鈕(或任何視圖)。當要放大按鈕時,則要傳送大於 1.0 的值,而小於1.0 的值,會讓按鈕變小。
.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
因此,這行程式碼的作用是,當按下按鈕時會縮小按鈕(即 0.9
),而當使用者放開手指後,會縮放回原來的大小(即 1.0
)。如果你已執行這個App,則在按鈕縮放時,你應該會看到一個不錯的動畫效果,這是 SwiftUI 的強大之處,你不需要額外撰寫程式碼,它已經內建了動畫特效。
作業
作業內容是建立一個具有+圖示的動畫按鈕,可以出現 + 圖示。當使用者按下按鈕時,這個+圖示將旋轉(順時針/ 逆時針)成一個 × 圖示,如圖 6.24 所示。
提示一下,rotationEffect
修飾器可以用來旋轉按鈕(或者其他視圖)。
在 iOS 15 中設置按鈕樣式
相信你已經知道如何實作如圖 25 所示的按鈕。在 iOS 15 中,Apple 為 Button
視圖引入了許多修飾符。 要建立類似按鈕,你可以編寫如下程式碼:
Button {
} label: {
Text("Buy me a coffee")
}
.tint(.purple)
.buttonStyle(.borderedProminent)
.buttonBorderShape(.roundedRectangle(radius: 5))
.controlSize(.large)
tint
修飾器指定按鈕的顏色。 通過應用.borderedProminent
樣式,iOS 將按鈕呈現為紫色背景並以白色顯示文字。 .buttonBorderShape
修飾器可讓你設置按鈕的邊框形狀。 在這裡,我們將它設置為 .roundedRectangle
使按鈕變圓角。
.controlSize
允許你更改按鈕的大小。 預設大小為 .regular
。 其他有效值包括 .large
、.small
和 .mini
。 下圖顯示了按鈕在不同尺寸下的外觀。
除了使用.roundedRectangle
,SwiftUI 還提供了另一種名為.capsule
的邊框形狀,供開發者創建膠囊形狀按鈕。
你還可以使用 .automatic
選項讓系統自動調整按鈕的形狀。到目前為止,我們使用.borderProminent
按鈕樣式。 新版 SwiftUI 還提供了其他內置樣式,包括 .bordered
、.borderless
和 .plain
。 .bordered
樣式是經常會使用的樣式。 下圖顯示了一個使用 .bordered
樣式的示例按鈕。
將樣式應用到多個按鈕
使用按鈕樣式,你可以輕鬆地將相同的樣式應用於一組按鈕。 下面是一個例子:
VStack {
Button(action: {}) {
Text("Add to Cart")
.font(.headline)
}
Button(action: {}) {
Text("Discover")
.font(.headline)
.frame(maxWidth: 300)
}
Button(action: {}) {
Text("Check out")
.font(.headline)
}
}
.tint(.purple)
.buttonStyle(.bordered)
.controlSize(.large)
使用按鈕角色
自 iOS 15 版本開始,SwiftUI 框架為Button
引入了一個新的role
選項。 此選項描述按鈕的語義角色。 根據指定的角色,iOS 會自動為按鈕呈現適當的外觀。
例如,如下所示將角色定義為 .destructive
:
Button("Delete", role: .destructive) {
print("Delete")
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
iOS 會自動以紅色顯示刪除 按鈕。 圖 29 顯示了不同角色和樣式的按鈕外觀。
本章小結
在本章中,我們介紹了 SwiftUI 中建立按鈕的基本觀念。按鈕在手機介面(或確切地說在任何應用程式 UI)中扮演著關鍵角色,良好設計的按鈕不僅可以使你的 UI 更具吸引力,還可以提升你的 App 的使用者體驗。如你所學,透過結合 SF Symbols、漸層與動畫的應用,你可以輕鬆建立一個有吸引力且實用的按鈕。
想更深入學習SwiftUI和下載完整程式碼?你可以從 AppCoda網站購買《精通 SwiftUI》完整電子版。