之前,我們向你講解了如何利用 UIKit 框架的UISearchBar
來實現搜索欄。 你有沒有想過從頭開始自己建立一個? 如果仔細看看搜尋欄,要自己做出來並不太難。 所以,讓我們在本章中嘗試構建一個 SwiftUI 版本的搜尋欄。
你不僅可以學習如何建立搜尋欄視圖,我們還將會和你講解如何使用自訂綁定。 之前我們討論過綁定,但還沒有向你展示如何建立自定義綁定。 當你需要為綁定(Binding)加入額外的程式邏輯時,自訂綁定就特別有用。 另外,你也會學習如何在 SwiftUI 中關閉軟體鍵盤。
圖 24.1 就是我們將要建立的搜索欄,外觀與 UIKit 中的 UISearchBar 非常相似。 同樣地,這個搜索欄也會有個 Cancel 按鈕,當使用者開始輸入搜尋文字時就會出現。
我們將會修改之前的項目轉為我們自己建立的搜尋欄。 因此,請首先從 https://www.appcoda.com/resources/swiftui5/SwiftUIToDoListUISearchBar.zip 下載啟動項目。 編譯一次以確保它有效。 該App應該向你顯示一個搜索欄,但是,該欄來自 UIKit。 我們將把它轉換成一個完全使用 SwiftUI 構建的搜尋欄視圖。
打開SearchBar.swift
,這是我們關注的文件。 我們將重寫整段程式碼,但保持其名稱不變。 我們仍然稱它為「SearchBar」,它仍然接受搜尋文字的綁定作為參數。 對於呼叫者(即 ContentView),沒有什麼需要變更, 用法還是這樣:
SearchBar(text: $searchText)
現在,讓我們從 UI 開始做起。 如果你想挑戰自己,請停止閱讀並嘗試自己實作搜尋欄 UI。 這個使用者介界非常簡單。 它由一個文字欄、幾個圖標和取消按鈕組成。
如果你不知道 UI 是如何建立的,就讓我們一起寫出來吧。 請將 SearchBar.swift
中的 SearchBar
結構改成這樣:
struct SearchBar: View {
@Binding var text: String
@State private var isEditing = false
var body: some View {
HStack {
TextField("Search ...", text: $text)
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundStyle(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundStyle(.gray)
.padding(.trailing, 8)
}
}
}
)
.padding(.horizontal, 10)
.onTapGesture {
withAnimation {
self.isEditing = true
}
}
if isEditing {
Button(action: {
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
}
}
}
}
首先,我們宣告了兩個變數:一個是搜尋文字的綁定,另一個則用於存放搜尋狀態(正在編輯與否)的變數。
我們使用 HStack
來佈局文字欄和 Cancel 按鈕。 對於文字欄,我們覆蓋了一個放大鏡圖案和交叉圖案(即multiply.circle.fill
),它僅在搜尋欄處於編輯模式時顯示。 Cancel 按鈕也是如此,當使用者點擊搜尋欄時才會出現。
為了預覽搜尋欄,還請加入以下程式碼:
#Preview {
SearchBar(text: .constant(""))
}
當加入程式碼後,你應該能夠預覽搜尋欄。 現在按播放 鈕進行測試。 當你選擇搜尋欄時,取消 按鈕就會出現。
更重要的是,搜尋欄已經可以使用了! 在模擬器上運行App並輸入搜尋字,它就會根據顯示相關的搜索結果。
如你所見,使用 SwiftUI 建立我們自己的搜尋欄並不太難。 在搜尋欄運作時,我們必須解決一個小問題。 你有否嘗試點擊取消按鈕? 它確實清除了搜尋文字。 然而,軟鍵盤並沒有消失。
為了解決這個問題,我們需要在 Cancel 按鈕的 action
中添加一行程碼:
// 關閉鍵盤
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
在程式碼中,我們呼叫了 sendAction
方法來關閉鍵盤。 你現在可以使用模擬器運行App。 當你點擊取消按鈕時,它就會清除搜尋文字並關閉軟體鍵盤。
SwiftUI 版本的搜尋欄已經可以正常使用了,不過我想藉此機會與你討論自訂綁定。 在 SearchBar.swift
,我們像這樣宣告搜尋文字的綁定:
@Binding var text: String
它非常適合我們當前程式的運作。 但是讓我來問問你, 如果我們在讀取或寫入此綁定時需要添加額外的邏輯,那可以怎麼辦? 例如,如何將使用者輸入的文字自動變成大寫?
Swift 有一個內置的功能可以將字串變為大寫。 你可以使用字串的 capitalized
屬性取得大寫文字串。但 問題是我們如何更新text
的綁定?
在這種情況下,你需要在SearchBar.swift
中建立一個自定義綁定,如下所示:
private var searchText: Binding<String> {
return Binding<String>(
get: {
self.text.capitalized
}, set: {
self.text = $0
}
)
}
在上面的程式碼中,我們建立了一個名為searchText
的自定義綁定,其中包含讀取和寫入綁定值的閉包。 對於get
部分,我們通過capitalized
屬性來自定義text
的綁定值。 這就是我們如何將使用者的搜尋字轉成大寫。 至於set
部分,我們不做任何更改,只將其設置為原始值。 但是,如果在設置綁定時需要添加額外的邏輯,就需要修改set
中的程碼。
題外話,你可以省略 return
關鍵字,像這樣寫就可以:
private var searchText: Binding<String> {
Binding<String>(
get: {
self.text.capitalized
}, set: {
self.text = $0
}
)
}
以防你沒有留意,這是自 Swift 5.1 就有的一項新功能,
我們仍在將 text
綁定傳遞給 TextField
。 在此更改生效之前,我們需要再進行一個小修改。 更改TextField
中的參數並確保將searchText
作為綁定傳遞:
TextField("Search ...", text: searchText)
現在在模擬器上運行該App。 在搜尋欄輸入幾個詞,App就會自動把每個單詞的第一個字母變成大寫。
在本章中,我們向你展示了另一種實現搜尋欄的方法。 如你所見,單是用 SwiftUI 建立一個搜尋欄並不困難。 你還學習了如何建立自定義綁定。 當你在設置或檢索綁定值時需要添加額外的程序時,這就非常有用。
在本章所準備的範例檔中,有最後完整的 Xcode 專案,可供你參考:
https://www.appcoda.com/resources/swiftui5/SwiftUIToDoListSearchBarView.zip