利用 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

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com