最近,我收到一個問題,關於在 SwiftUI 專案中實作 Search Bar。與 UIKit 不同,SwiftUI 沒有內建的 Search Bar 物件可以使用。你或許可以使用
協定,以在 SwiftUI 專案中重用 UIKit 的 UIViewRepresentable
UISearchBar
。但要使用純 SwiftUI 的方式來實作一個 Search Bar,其實並不困難。在這次的教學中,就讓我們來建立一個 SwiftUI 版的 Search Bar!
看看以下這張圖片,我們即將要打造一個這樣的 Search Bar。它看起來跟 UIKit 的 UISearchBar
沒什麼兩樣。而我們還會實作一個 Cancel 按鈕,按鈕只會在使用者開始在搜索欄打字時才會出現。
實作 Search Bar UI
為了讓你專注於實作 Search Bar,你可以先下載這個初始專案,並編譯一次,以確保它執行無誤。這個 App 會顯示一個待辦事項的列表。現在,讓我們實作 SearchBar.swift
檔案來建立一個 Search Bar。
仔細觀察一下 iOS 裡面內建的標準 Search Bar,你會發現它是由一個文字輸入框 (text field) 和 Cancel 按鈕所組成的。
import SwiftUI
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)
.padding(.horizontal, 10)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
}
}
首先,我們定義了兩個變數:一個是搜尋文字的 Binding,另一個用來儲存目前搜索欄的狀態(是否正在編輯中)。
接著,我們使用了 HStack
來編排文字輸入框跟 Cancel 按鈕的位置。按鈕只會在使用者點擊了搜索欄時才會顯示。為了預覽我們實作的 Search Bar,請輸入以下的程式碼:
struct SearchBar_Previews: PreviewProvider {
static var previews: some View {
SearchBar(text: .constant(""))
}
}
加入了這些程式碼之後,你應該能夠預覽整個搜索欄。點擊 Play 按鈕來測試這個搜索欄。現在,當你點擊文字輸入框之後,就會出現 Cancel 按鈕了。
目前這個版本的 Search Bar,還缺少了兩個要素:就是 Search Icon 和 Cross Icon。我們可以在文字輸入框附加 overlay
修飾器,來加入這些 icon。將以下的程式碼放在 .cornerRadius(8)
的後面:
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
我們為文字輸入框加上了兩個系統圖片,而 multiply.circle.fill
只會在使用者開始在搜索欄打字時顯示。當這個圖片被點擊時,它會將搜索欄重設成空白的狀態。你可以點擊 Play 按鈕,以在預覽視窗測試 Search Bar。
使用 Search Bar 來過濾資料
現在,我們已經準備好 Search Bar。接下來,讓我們切換到 ContentView.swift
,將它加入到 list view 裡面吧。
在 List
View 之前,加入以下的程式碼:
SearchBar(text: $searchText)
.padding(.top, -30)
這樣,我們就功成在 title 跟 list view 之間加上了 Search Bar。searchText
是一個狀態變數 (state variable),持有我們即將搜尋的文字。它會隨著使用者的在搜索欄所輸入的內容,來更新要搜尋的文字。
我們需要如此更新 List
view 來過濾結果:
List(todoItems.filter({ searchText.isEmpty ? true : $0.name.contains(searchText) })) { item in
Text(item.name)
}
在這段程式碼中,我們使用了 filter
函式來尋找符合搜尋文字的待辦事項。在這一段閉包中,我們先檢查了 searchText 有沒有值。如果沒有的話,我們就回傳 true
,也就是會回傳全部的待辦事項。相反,如果 searchText 有值的話,就檢查待辦事項的名稱欄位是否有包含搜尋的文字。
讓我們試試執行 App 吧,結果應該會依照你所輸入的文字來找到待辦事項。
將鍵盤收起來
如你所見,完全用 SwiftUI 打造一個 Search Bar,並不是困難的事情。可是,雖然現在它可以正常運作,但還有一個小問題需要修正。不知道你有沒有嘗試點擊 Cancel 按鈕?它的確會清除搜索欄的內容,但是虛擬鍵盤卻沒有消失。
為了修正這一點,我們需要在 Cancel 按鈕的 action
block 加上一行程式碼:
// Dismiss the keyboard
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
在這一段程式碼中,我們呼叫了 sendAction
方法,來解除目前的 first responder,並收起了虛擬鍵盤。現在你可以重新執行模擬器,你會發現當你點擊 Cancel 按鈕時,它就會清除搜索欄,並且將虛擬鍵盤收起來了。
總結
在這篇教學中,我們展示了如何實作一個 Search Bar。如你所見,這個過程並不困難。如果你想深入鑽研管理資料庫內容的技巧,可以參考我們的 《精通SwiftUI》電子書。在書本中,我們會更進一步地展示如何使用 Core Data 來保留資料,以及使用 fetch request 來處理搜尋的功能,而且你還可以取得一個待辦事項 App 完整的原始碼。
你可以在這邊下載完整專案作參考。
原文:Building a Search Bar in SwiftUI for iPad and Mac