空白狀態 (Empty State) 是 UX 的一個重要元素,是使用者初次打開我們的 App 時看到的東西,要留下好的第一印象,機會就只有一次,因此一個好的空白狀態很重要。一個有意義的空白狀態,可以讓使用者感到被歡迎,我們也可以藉著這個機會,教使用者如何使用我們的 App。
現在,我們的範例 App Make It So 還沒有一個有意義的空白狀態。在這篇文章中,我們將會利用 SwiftUI,探究實作空白狀態的不同方法。
我們的範例
首先,讓我們來看看 Apple 的提醒事項 App,因為這是我們想要複製的 App。
有一個簡單的方法可以實作空白狀態,就是使用 Xcode 的 Make Conditional 重構 (refactoring)(CMD + 點擊視圖,然後選擇 Make Conditional)。如此一來,視圖就會被包裝 (wrap) 在 if ... else
語句中,並將整個結構包裝在 VStack
中:
struct ContentView: View {
@State var isEmpty = true
var body: some View {
VStack {
if isEmpty {
Text("Hello, World!")
} else {
EmptyView()
}
}
}
}
這個方法不是不好,但會給視圖的程式碼增加視覺干擾 (visual noise)。接下來,讓我們看看如何可以改善這一點吧!
利用 ViewModifier 來管理空白狀態
視圖修飾符 (ViewModifier) 是 SwiftUI 其中一個主要的功能,讓我們可以愉快地編寫 SwiftUI 程式碼。如果沒有視圖修飾符,我們就只可以利用初始化器 (initialisers) 配置視圖,這樣會嚴重影響開發者體驗。
幸好有視圖修飾符,我們可以如此配置視圖:
Text("Hello, World!")
.foregroundColor(.red)
.font(.title)
.opacity(75)
而不用這樣配置(假設情況):
Text("Hello, World!", foregroundColor: .red, font: .title, opacity: 0.75)
在 Call Site 中,用於向視圖添加空白狀態的視圖修飾符會是這樣的:
Text("Hello, World!")
.emptyState($isEmpty) {
Text("Sorry - no content available")
}
讓我們來看看如何構建它。視圖修飾符通常由兩部分組成:視圖修飾符本身、和讓修飾符更易於使用的 Extension。讓我們從視圖修飾符開始:
struct EmptyStateViewModifier<EmptyContent>: ViewModifier where EmptyContent: View {
var isEmpty: Bool
let emptyContent: () -> EmptyContent
func body(content: Content) -> some View {
if isEmpty {
emptyContent()
}
else {
content
}
}
}
所有視圖修飾符都可以通過 body
函式的 content: Content
參數 (parameter) 存取操作的視圖。此外,我們還宣告了兩個屬性 (property):
isEmpty
:讓呼叫者指示是否顯示空白狀態emptyContent
: 這是一個閉包 (closure),如果isEmpty
是true
,就會回傳我們想顯示的視圖
如果我們要在視圖上使用這個修飾符,就要如此編寫程式碼:
Text("Hello, World!")
.modifier(EmptyStateViewModifier(isEmpty: isEmpty, emptyContent: {
Text("Sorry - no content available")
}))
這樣看起來有點難使用。
添加 Extension 來完善開發者體驗
我們在 View
宣告一個 Extension,讓視圖修飾符更易於使用:
extension View {
func emptyState<EmptyContent>(_ isEmpty: Bool,
emptyContent: @escaping () -> EmptyContent) -> some View where EmptyContent: View {
modifier(EmptyStateViewModifier(isEmpty: isEmpty, emptyContent: emptyContent))
}
}
現在我們可以按預期使用視圖修飾符。以下就是 Make It So 的主列表視圖,當中應用了視圖修飾符:
List {
ForEach($viewModel.tasks) { $task in
TaskListRowView(task: $task)
.focused($focusedTask, equals: .row(id: task.id))
.onSubmit {
viewModel.createNewTask()
}
.swipeActions {
Button(role: .destructive, action: { viewModel.deleteTask(task) }) {
Label("Delete", systemImage: "trash")
}
Button(action: { viewModel.flagTask(task) }) {
Label("Flag", systemImage: "flag")
}
.tint(Color(UIColor.systemOrange))
Button(action: {}) {
Label("Details", systemImage: "ellipsis")
}
.tint(Color(UIColor.systemGray))
}
}
}
.emptyState($viewModel.tasks.isEmpty) {
Text("No Reminders")
.font(.title3)
.foregroundColor(Color.secondary)
}
你可以從這個 GitHub 程式庫中的 develop 分頁中,找到 Make It So 和視圖修飾符的源程式碼。
謝謝你的閱讀。