利用 UISearchController 實現 search 功能
開發 iOS App 時,我們時常需要實現搜尋資料的功能。如下圖所示,在 iOS App 時常會看到長在 navigation bar 的 search bar。
在 search bar 輸入文字時,search bar 還會自動移到畫面上方,挪出更多空間呈現資料內容。
以上的功能透過 UISearchController 將可快速實現。接下來我們將示範利用 UISearchController 實現長在 navigation bar 的 search bar。
在加入 search 功能前,先讓我們利用表格顯示情歌王子林隆璇的那些歌兒。
import UIKit
class LoveSongTableViewController: UITableViewController {
let songs = ["為愛往前飛", "我愛你這樣深", "難以抗拒你容顏", "流言", "你那麼愛她", "我還是愛你到老", "我是多麼認真對你", "體會", "怎麼開始忘了", "藏經閣", "白天不懂夜的黑"]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return songs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LoveSongCell", for: indexPath)
let song = songs[indexPath.row]
cell.textLabel?.text = song
return cell
}
}
在 navigation bar 顯示 search bar
設定 navigation item 的 searchController。
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController()
navigationItem.searchController = searchController
}
在 search bar 輸入文字時,bar 將自動移到畫面上方。
畫面捲動時是否顯示 search bar
當我們捲動畫面時,預設 search bar 會隨著捲動隱藏在 navigation bar 下。
將 navigationItem 的 hidesSearchBarWhenScrolling 設為 false,畫面捲動時 search bar 將持續顯示在 navigation bar 上。
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController()
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
現在畫面上有了 search bar,不過它是騙人的,不管我們打什麼字,表格還是顯示原本的內容,接下來我們將進入重頭戲,真正地實現 search 功能。
實現 search 功能
將 LoveSongTableViewController 設為 UISearchController 的 searchResultsUpdater。
class LoveSongTableViewController: UITableViewController {
let songs = ["為愛往前飛", "我愛你這樣深", "難以抗拒你容顏", "流言", "你那麼愛她", "我還是愛你到老", "我是多麼認真對你", "體會", "怎麼開始忘了", "藏經閣", "白天不懂夜的黑"]
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController()
searchController.searchResultsUpdater = self
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
然後讓 LoveSongTableViewController 遵從 protocol UISearchResultsUpdating,定義 function updateSearchResults(for:)。
extension LoveSongTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
}
}
當我們在 search bar 輸入文字時,它將觸發 UISearchResultsUpdating 的 function updateSearchResults(for:),因此我們可在 function 裡依據 search bar 的文字調整表格對應的 array 內容。
在 LoveSongTableViewController 宣告 property filteredSongs,利用它儲存表格顯示的 array 內容。當使用者沒有 search 時,filteredSongs 的內容將是 songs,也就是全部的歌曲。
class LoveSongTableViewController: UITableViewController {
let songs = ["為愛往前飛", "我愛你這樣深", "難以抗拒你容顏", "流言", "你那麼愛她", "我還是愛你到老", "我是多麼認真對你", "體會", "怎麼開始忘了", "藏經閣", "白天不懂夜的黑"]
lazy var filteredSongs = songs
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController()
searchController.searchResultsUpdater = self
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSongs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LoveSongCell", for: indexPath)
let song = filteredSongs[indexPath.row]
cell.textLabel?.text = song
return cell
}
}
值得注意的,宣告 filteredSongs 時,我們特別加上 lazy。它將讓程式更有效率,在第一次存取 filteredSongs 時才設定 filteredSongs 的內容。
依據 search bar 的文字調整 filteredSongs,然後呼叫 reloadData 更新表格。
extension LoveSongTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text,
searchText.isEmpty == false {
filteredSongs = songs.filter({ song in
song.localizedStandardContains(searchText)
})
} else {
filteredSongs = songs
}
tableView.reloadData()
}
}
完整程式
import UIKit
class LoveSongTableViewController: UITableViewController {
let songs = ["為愛往前飛", "我愛你這樣深", "難以抗拒你容顏", "流言", "你那麼愛她", "我還是愛你到老", "我是多麼認真對你", "體會", "怎麼開始忘了", "藏經閣", "白天不懂夜的黑"]
lazy var filteredSongs = songs
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController()
searchController.searchResultsUpdater = self
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return filteredSongs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LoveSongCell", for: indexPath)
let song = filteredSongs[indexPath.row]
cell.textLabel?.text = song
return cell
}
}
extension LoveSongTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text,
searchText.isEmpty == false {
filteredSongs = songs.filter({ song in
song.localizedStandardContains(searchText)
})
} else {
filteredSongs = songs
}
tableView.reloadData()
}
}
以另一個 view controller 顯示搜尋結果
若是搜尋結果想要呈現不同的畫面,我們可在產生 UISearchController 時傳入顯示結果的 controller。
比方我們想以 ResultsTableController 顯示搜尋結果,它的 cell 是黃色。
ResultsTableController 的定義如下,儲存搜尋結果的 filteredSongs 改成宣告在 ResultsTableController。
import UIKit
class ResultsTableController: UITableViewController {
var filteredSongs = [String]()
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSongs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LoveSongCell", for: indexPath)
let song = filteredSongs[indexPath.row]
cell.textLabel?.text = song
return cell
}
}
LoveSongTableViewController 的定義如下,產生 UISearchController 時在參數 searchResultsController 傳入 ResultsTableController。
import UIKit
class LoveSongTableViewController: UITableViewController {
var resultsTableController: ResultsTableController!
let songs = ["為愛往前飛", "我愛你這樣深", "難以抗拒你容顏", "流言", "你那麼愛她", "我還是愛你到老", "我是多麼認真對你", "體會", "怎麼開始忘了", "藏經閣", "白天不懂夜的黑"]
override func viewDidLoad() {
super.viewDidLoad()
resultsTableController = storyboard?.instantiateViewController(withIdentifier: "\(ResultsTableController.self)") as? ResultsTableController
let searchController = UISearchController(searchResultsController: resultsTableController)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return songs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LoveSongCell", for: indexPath)
let song = songs[indexPath.row]
cell.textLabel?.text = song
return cell
}
}
extension LoveSongTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
if let searchText = searchController.searchBar.text,
searchText.isEmpty == false {
resultsTableController.filteredSongs = songs.filter({ song in
song.localizedStandardContains(searchText)
})
} else {
resultsTableController.filteredSongs = []
}
resultsTableController.tableView.reloadData()
}
}
UISearchController 的其它功能
- automaticallyShowsCancelButton
是否顯示 cancel button。下圖為 automaticallyShowsCancelButton 設為 false,不顯示 cancel button。
- hidesNavigationBarDuringPresentation
在 search bar 輸入文字時,是否隱藏 navigation bar 的標題。下圖為 hidesNavigationBarDuringPresentation 設為 false,在 search bar 輸入文字時顯示 navigation bar 的標題。
利用 debounce 優化 search 時發送的 request
參考範例
Apple 的 Using Suggested Searches with a Search Controller
Apple 的 Displaying Searchable Content by Using a Search Controller