Toggle 是 SwiftUI 中很常用的視圖,讓使用者在開或關兩種狀態之間切換。這個控件 (control) 可以為使用者提供簡單而直觀的界面,因此很多開發者都會用到它。舉個例子,我們可以在 App 中使用 Toggle,來控制開啟或關閉某些功能,或切換不同模式或選項。要在 SwiftUI App 中實作 Toggle 十分簡單,我們可以使用 ToggleStyle
協定,來創建適合我們 App 設計和風格的 Toggle。
在這篇教學文章中,我會帶大家了解這個 Toggle
視圖,並一起利用 ToggleStyle
構建幾個客製化 Toggle。
Toggle 的基礎使用
在 SwiftUI 中,我們有幾個方法可以使用 Toggle
視圖。最基本的使用方法是這樣的:
struct ContentView: View {
@State private var isEnabled = false
var body: some View {
Toggle("Airplane Mode", isOn: $isEnabled)
}
}
在這裡,我們使用 text description 和 binding 來初始化 Toggle 視圖,而 Toggle 的狀態是由狀態變數 (state variable) 所控制的。這段程式碼會建構出上圖那個帶有 text label 的基本 toggle。
如果我們想客製化文本的樣式,Toggle
還有另一個初始化器 (initializer)。我們可以在閉包中這樣設定 Text
視圖的外觀:
Toggle(isOn: $isEnabled) {
Text("Airplane mode")
.font(.system(.title, design: .rounded))
.bold()
}
我們還可以這樣客製化 description,來添加一個圖像:
Toggle(isOn: $isEnabled) {
HStack {
Text("Airplane mode")
Image(systemName: "airplane")
}
.font(.system(size: 20))
}
最後我們會看到一個更漂亮的 switch。
改變 Toggle 的樣式
在預設設定下,Toggle
視圖是使用 switch 樣式的。我們可以使用 .toggleStyle
修飾符,來把樣式從 switch 轉換成 button:
.toggleStyle(.button)
比如說,如果我們想建立一個書籤按鈕,就可以使用以下的程式碼:
struct ContentView: View {
@State private var isBookmarked = false
var body: some View {
Toggle(isOn: $isBookmarked) {
Image(systemName: isBookmarked ? "bookmark.fill" : "bookmark")
.font(.system(size: 50))
}
.tint(.green)
.toggleStyle(.button)
.clipShape(Circle())
}
}
整個操作十分相似,只是這次我們會用 .toggleStyle
修飾符,來告訴 Toggle
使用 .button
樣式。如此一來,SwiftUI 就不會把 toggle 顯示為一個開關,而是會顯示為一個按鈕,而按鈕會根據狀態改變其外觀。
使用 ToggleStyle 來建立一個客製化 Toggle
在大部分情況下,預設的 toggle 樣式都足夠應付我們的需要。不過,在某些情況下,我們可能想要創建客製化的樣式,來切合 App 的風格。接下來,讓我們看看如何創建自己的客製化 toggle 吧!簡單來說,我們可以採用 ToggleStyle
協定,並實作所需的 makeBody(configuration:)
函數,來創建自己的樣式:
struct CustomToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
// Your implementation
}
}
讓我們從簡單的開始!首先,我們會構建一個 toggle switch,並客製化其背景顏色和符號。
要構建這個 toggle switch,讓我們建立一個新的結構 SymbolToggleStyle
,它符合 ToggleStyle
協定:
struct SymbolToggleStyle: ToggleStyle {
var systemImage: String = "checkmark"
var activeColor: Color = .green
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
Spacer()
RoundedRectangle(cornerRadius: 30)
.fill(configuration.isOn ? activeColor : Color(.systemGray5))
.overlay {
Circle()
.fill(.white)
.padding(3)
.overlay {
Image(systemName: systemImage)
.foregroundColor(configuration.isOn ? activeColor : Color(.systemGray5))
}
.offset(x: configuration.isOn ? 10 : -10)
}
.frame(width: 50, height: 32)
.onTapGesture {
withAnimation(.spring()) {
configuration.isOn.toggle()
}
}
}
}
}
這個結構有兩個參數,一個是符號的圖像名稱,另一個是 switch 的顏色。我們會在 makeBody
函數中從頭開始構建 toggle,包括將 text label 放在一側,switch 放在另一側。
configuration
參數為我們提供了兩個信息:toggle 的 text label(即 configuration.label
)和狀態(即 configuration.isOn
)。在上面的程式碼中,我們使用 HStack
排列 text label 和圓角矩形。另外,我們在圓角矩形上添加一個圓形來構建 switch,並在圓形上面疊加一個圖像視圖來顯示符號。在預設情況下,圖像設置為顯示一個 checkmark。
如我剛剛所說,我們會從頭開始創建一個 switch,因此我們需要處理點擊手勢 (tap gesture),並切換 switch 的狀態。此外,當狀態改變時,我們會改變 offset value 來移動圓形。
我們可以這樣套用 toggleStyle
修飾符,來使用這個客製化 toggle 樣式:
Toggle(isOn: $isEnabled) {
Text("Airplane mode")
}
.toggleStyle(SymbolToggleStyle(systemImage: "airplane", activeColor: .purple))
以上的程式碼會利用 SymbolToggleStyle
來創建一個 toggle。
建立一個動畫化的圖像 Toggle
接下來,讓我們構建另一個 toggle 樣式,它在開啟和關閉的狀態下會顯示不同的圖像。在這個專案中,我們會使用以下這兩個圖像:
- 由 pikisuperstar 在 Freepik 提供的圖像(請把圖像命名為 "light.jpg")
- 由 pikisuperstar 在 Freepik 提供的圖像(請把圖像命名為 "dark.jpg")
下載以上兩張圖像後,把它們匯入到 SwiftUI 專案的 asset catalog 中。然後,我們這樣建立新的 toggle 樣式:
struct ImageToggleStyle: ToggleStyle {
var onImage = "dark"
var offImage = "light"
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
Spacer()
RoundedRectangle(cornerRadius: 30)
.fill(configuration.isOn ? .black : Color(.systemGray5))
.overlay {
Image(configuration.isOn ? onImage : offImage)
.resizable()
.scaledToFill()
.clipShape(Circle())
.padding(5)
.rotationEffect(.degrees(configuration.isOn ? 0 : -360))
.offset(x: configuration.isOn ? 10 : -10)
}
.frame(width: 50, height: 32)
.onTapGesture {
withAnimation(.spring()) {
configuration.isOn.toggle()
}
}
}
}
}
ImageToggleStyle
結構有開啟和關閉圖像的兩個參數。實作 switch 的步驟和前文非常相似,只是我們這次不會使用 Circle
視圖來創建 switch,而是使用圖像視圖來顯示開啟和關閉的圖像。
我們也添加了 rotationEffect
修飾符,為 switch 的狀態轉換設置動畫。
同樣地,如果要使用這個 toggle 樣式,我們只需要把 .toggleStyle
修飾符附加到 toggle
視圖即可:
VStack {
Toggle(isOn: $isEnabled) {
Text("Light/Dark mode")
.foregroundColor(isEnabled ? .white : .black)
}
.toggleStyle(ImageToggleStyle())
.padding()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(isEnabled ? .black.opacity(0.6) : .white)
簡單存取 Toggle 樣式
為了簡化存取客製化 toggle 樣式的步驟,我們可以在 ToggleStyle
的 extension 添加一個狀態變數:
extension ToggleStyle where Self == ImageToggleStyle {
static var image: ImageToggleStyle { .init() }
}
在這個情況下,我們可以使用 dot syntax 應用 toggle 樣式:
Toggle(isOn: $isEnabled) {
Text("Light/Dark mode")
.foregroundColor(isEnabled ? .white : .black)
}
.toggleStyle(.image)
總結
在這篇教學文章中,我們學會了如何在 SwiftUI 創建客製化 Toggle 的樣式。我們可以使用 ToggleStyle
協定,來製作出獨特而漂亮的 Toggle。如果大家想更深入了解 SwiftUI 和 ToggleStyle
協定,可以參閱我們的《精通SwiftUI》一書,當中會有更多技巧和完整的源程式碼。