在任何使用者介面中,「焦點(Focus)」在確定哪個元素接收下一個輸入的過程中起到了關鍵性的作用。SwiftUI 提供了一套強大的工具和視圖修飾器,使你能在你的應用程式中控制並管理「焦點」。通過使用這些修飾器,你可以指出哪些視圖有資格接收「焦點」,偵測哪個視圖目前擁有「焦點」,甚至可以程式化地控制「焦點」狀態。
在這篇教程中,我們將探討 SwiftUI 的「焦點」管理API的細節,讓你有能力創造出吸引人且互動的使用者體驗。具體來說,我們將深入探討關鍵屬性包裝器的使用,像是@FocusState
、@FocusedValue
和@FocusObject
。
處理@FocusState
首先,讓我們開始講解@FocusState
。有了這個包裹器(Property Wrapper),開發者可以輕鬆地管理特定視圖的「焦點」並追蹤視圖目前是否擁有「焦點」。為了觀察和更新視圖的「焦點」狀態,我們通常使用focused
修改器與@FocusState
屬性包裹器一起使用。利用這些API,你將獲得對SwiftUI視圖的「焦點」行為的精確控制。
為了讓你更清楚地理解focused
和@FocusState
如何共同作用,讓我們來看一個例子。
struct FocusStateDemoView: View {
@State private var comment: String = ""
@FocusState private var isCommentFocused: Bool
var body: some View {
VStack {
Text("👋Help us improve")
.font(.system(.largeTitle, design: .rounded, weight: .black))
TextField("Any comment?", text: $comment)
.padding()
.border(.gray, width: 1)
.focused($isCommentFocused)
Button("Submit") {
isCommentFocused = false
}
.controlSize(.extraLarge)
.buttonStyle(.borderedProminent)
}
.padding()
.onChange(of: isCommentFocused) { oldValue, newValue in
print(newValue ? "Focused" : "Not focused")
}
}
}
在上述的程式碼中,我們創建了一個簡單的表單,其中有一個“評論”文字欄位。我們有一個名為isCommentFocused
的屬性,這個屬性用@FocusState
註釋來追蹤文字欄位的「焦點」狀態。對於“評論”欄位,我們加上了focused
修飾器並綁定到isCommentFocused
屬性。
透過這樣做,SwiftUI自動監控“評論”欄位的「焦點」狀態。當欄位進入「焦點」時,isCommentFocused
的值將設為真實。相反地,當欄位失去「焦點」時,該值將更新為假。你也可以通過更新其值來程式化地控制文字欄位的「焦點」。例如,當提交按鈕被輕觸時,我們通過設置isCommentFocused
為false
來重設「焦點」。
onChange
修飾器被用來揭示「焦點」狀態的變化。它監控isCommentFocused
變數並打印其值。
當你在預覽窗格中測試應用程式示範時,控制台應該在“評論”欄位的狀態為「焦點」時顯示“Focused”訊息。此外,輕觸提交按鈕應該會觸發“Not focused”的訊息。
使用Enum來管理「焦點」狀態
當你只需要追蹤單個文字欄位的「焦點」狀態時,使用布爾變數效果出色。然而,當你需要同時處理多個文字欄位的「焦點」狀態時,這種方式可能變得繁瑣。
你可以定義一種符合Hashable
的enum類型,而非布爾變數,來管理多個文字欄位(或SwiftUI視圖)的「焦點」狀態。
讓我們繼續以同樣的應用程式示範來說明這種技術。我們將在表單視圖中新增兩個文字欄位,包括名稱和電子郵件。這是修改後的程式:
struct FocusStateDemoView: View {
enum Field: Hashable {
case name
case email
case comment
}
@State private var name: String = ""
@State private var email: String = ""
@State private var comment: String = ""
@FocusState private var selectedField: Field?
var body: some View {
VStack {
Text("👋Help us improve")
.font(.system(.largeTitle, design: .rounded, weight: .black))
TextField("Name", text: $name)
.padding()
.border(.gray, width: 1)
.focused($selectedField, equals: .name)
TextField("Email", text: $email)
.padding()
.border(.gray, width: 1)
.focused($selectedField, equals: .email)
TextField("Any comment?", text: $comment)
.padding()
.border(.gray, width: 1)
.focused($selectedField, equals: .comment)
Button("Submit") {
selectedField = nil
}
.controlSize(.extraLarge)
.buttonStyle(.borderedProminent)
}
.padding()
.onChange(of: selectedField) { oldValue, newValue in
print(newValue ?? "No field is selected")
}
}
}
為了有效地管理多個文字欄位的「焦點」,我們避免定義額外的 Boolean 變數,而是引入了一種名為Field
的enum類型。這個enum符合Hashable
協定,並定義了三種情況,每一種情況代表表單中的一個文字欄位。
利用這個enum,我們使用@FocusState
屬性包裝器來宣告selectedField
屬性。這個屬性讓我們能夠方便地追蹤目前擁有「焦點」的文字欄位。
為了建立連接,每個文字欄位都與focused
修飾器相關,該修飾器使用對應的值來綁定到「焦點」狀態屬性。例如,當「焦點」移到“評論”欄位時,綁定會將綁定值設置為.comment
。
你現在可以測試程式碼的更改。當你輕觸任何欄位時,控制台將顯示相關文字欄位的名稱。然而,如果你輕觸提交按鈕,控制台將顯示 “No field is selected” 的訊息。
你可以程式化地更改文字欄位的「焦點」。讓我們修改Submit按鈕的"行動"區塊,如下所示:
Button("Submit") {
selectedField = .email
}
透過為提交按鈕將selectedField
的值設為.email
,當輕觸提交按鈕時,應用程式將自動將「焦點」移至電子郵件欄位。
使用 FocusedValue
現在你應該理解@FocusState
的工作方式,我們接著來看看下一個屬性包裝器@FocusedValue
。該屬性包裝器允許開發者監控當前擁有焦點的文字欄位(或其他可焦點的視圖)的值。
為了更好地了解使用方法,讓我們繼續在範例中進行操作。假設,我們想在表單下方添加一個預覽部分,以顯示用戶的評論,但我們只希望在評論欄位有焦點時,評論才可見。以下是預覽部分的程式碼範例:
struct CommentPreview: View {
var body: some View {
VStack {
Text("")
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 100)
.padding()
.background(.yellow)
}
}
我們將預覽部分放在Submit按鈕的正下方,如下所示:
struct FocusStateDemoView: View {
...
var body: some View {
VStack {
.
.
.
Button("Submit") {
selectedField = nil
}
.controlSize(.extraLarge)
.buttonStyle(.borderedProminent)
Spacer()
CommentPreview()
}
.padding()
.onChange(of: selectedField) { oldValue, newValue in
print(newValue ?? "No field is selected")
}
}
}
為了監控評論欄位的變化,我們首先創建一個符合FocusedValueKey
協議的結構。在這個結構中,我們定義了要監視的值的類型。在這個例子中,評論的類型是String
。
struct CommentFocusedKey: FocusedValueKey {
typealias Value = String
}
接下來,我們為FocusedValues
提供了一個擴展,該擴展有一個計算屬性,該屬性使用新的鍵來獲取和設置值。
extension FocusedValues {
var commentFocusedValue: CommentFocusedKey.Value? {
get { self[CommentFocusedKey.self] }
set { self[CommentFocusedKey.self] = newValue }
}
}
一旦你設定好所有這些,你就可以將focusedValue
修改器附加到“評論”文本欄位,並指定觀察評論的值。
TextField("Any comment?", text: $comment)
.padding()
.border(.gray, width: 1)
.focused($selectedField, equals: .comment)
.focusedValue(\.commentFocusedValue, comment)
現在回到CommentPreview
結構,並使用@FocusedValue
屬性包裝器聲明一個comment
屬性:
struct CommentPreview: View {
@FocusedValue(\.commentFocusedValue) var comment
var body: some View {
VStack {
Text(comment ?? "Not focused")
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 100)
.padding()
.background(.yellow)
}
}
我們使用 @FocusedValue
屬性包裝器來監控並在評論欄位處於焦點時檢索其最新的值。
現在,當你在評論欄位中輸入任何文字時,預覽區域應該會顯示相同的值。然而,當你離開評論欄位時,預覽區域將會顯示"Not focused"的訊息。
使用 @FocusedObject
@FocusedValue
用於監視值類型的變化。對於參考類型,你可以使用另一個名為@FocusedObject
的屬性包裝器。假設,你希望在評論欄位上方的預覽區域顯示名稱和電子郵件欄位的內容。
為了做到這一點,你可以定義一個符合ObservableObject
協議的類,像這樣:
class FormViewModel: ObservableObject {
@Published var name: String = ""
@Published var email: String = ""
@Published var comment: String = ""
}
在表單視圖中,我們可以為視圖模型宣告一個狀態物件:
@StateObject private var viewModel: FormViewModel = FormViewModel()
要將可觀察對象與焦點關聯,我們將focusedObject
修飾器附加到下面的文本字段:
TextField("Name", text: $viewModel.name)
.padding()
.border(.gray, width: 1)
.focused($selectedField, equals: .name)
.focusedObject(viewModel)
TextField("Email", text: $viewModel.email)
.padding()
.border(.gray, width: 1)
.focused($selectedField, equals: .email)
.focusedObject(viewModel)
TextField("Any comment?", text: $viewModel.comment)
.padding()
.border(.gray, width: 1)
.focused($selectedField, equals: .comment)
.focusedObject(viewModel)
對於 CommentPreview
結構,我們使用 @FocusedObject
屬性包裝器來獲取值的變化:
struct CommentPreview: View {
@FocusedObject var viewModel: FormViewModel?
var body: some View {
VStack {
Text(viewModel?.name ?? "Not focused")
Text(viewModel?.email ?? "Not focused")
Text(viewModel?.comment ?? "Not focused")
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 100)
.padding()
.background(.yellow)
}
}
總結
這個教程解釋了如何使用 SwiftUI 的焦點管理 API,特別是 @FocusState
,@FocusedValue
和 @FocusedObject
。通過利用這些包裝器,你可以有效地監視焦點狀態的變化並訪問可獲取焦點的視圖的值。這些強大的工具使開發人員能夠在各種平台上提供增強的用戶體驗,包括 iOS,macOS 和 tvOS 應用程序。
我希望你喜歡這篇教程。如果你有任何問題,請在下面留言。