UISearchDisplayControllerを実装する

連絡帳の連絡先一覧の上部なんかについている検索バーと同じものを実装します。

実装

前提

UITableViewが適切に動作するアプリケーションを修正するという形で実装を行います。

そのアプリケーションにUISearchDisplayControllerを配置し、Outlet接続したところからスタートです。

ここでは、mySearchDisplayControllerという名前で接続したものとします。

1.delegateとかの指定

viewDidLoadイベントとかでdelegateの指定をします。

mySearchDisplayController.searchResultsDelegate = self
mySearchDisplayController.searchResultsDataSource = self
mySearchDisplayController.delegate = self

ここで、searchResultsDelegate, searchResultsDataSourceに指定するのは、UITableViewに指定しているdelegate, datasourceにします。

また、selfにはUISearchDisplayDelegateも継承させる必要があります。

2.検索結果を保持するデータリストの作成

selfに検索結果を保持するためのリストを追加します。

var searchResults = [""]

3.TableViewの処理を修正する

通常表示の場合と検索中の表示の場合とで表示内容が異なりますから、実装を一部書き換えます。

今回は以下のように書き換えました。

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if(tableView == self.tableView) {
        // 通常のTableView
        return texts.count
    } else {
        // 検索結果TableView
        return searchResults.count
    }
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as UITableViewCell
    if(tableView == self.tableView) {
        // 通常のTableView
        cell.textLabel?.text = texts[indexPath.row]
    } else {
        // 検索結果TableView
        cell.textLabel?.text = searchResults.objectAtIndex(indexPath.row) as? String
    }
    return cell
}

引数のtableViewには、通常状態であれば通常画面のUITableViewが、検索中であれば検索結果表示専用のTableViewが入っているため、参照の比較により状態を判定することができます。

ここで、self.tableViewとせずtableViewとすると、検索結果表示時にdequeReusableCellWithIdentifierが失敗し、クラッシュします。

4.検索処理の実装

まず、検索処理を実際に行うヘルパーメソッドを作成します。

func filterContentForSearchText(searchText: String) {
    self.searchResult = self.texts.filter({(text: String) -> Bool in
        let stringMatch = text.rangeOfString(searchText)
        return (stringMatch != nil)
    })
}

これを、検索キーワード変化時に毎回呼び出すようにします。

func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String!) -> Bool {
    self.filterContentForSearchText(searchString)
    return true
}
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchScope searchOption: Int) -> Bool {
    self.filterContentForSearchText(self.searchDisplayController!.searchBar.text)
    return true
}

5.カスタムCellを利用している場合の補足

カスタムCellを利用して高さを変更している場合、検索結果TableViewの高さがそれに適合してくれません。 そこで、生成時にUITableViewの高さを参照して高さを決定する処理を加えます。

func searchDisplayController(controller: UISearchDisplayController, didLoadSearchResultsTableView tableView: UITableView) {
    tableView.rowHeight = self.tableView.rowHeight
}

解説

UISearchDisplayControllerは、検索バーに文字列が入力されると、検索結果を表示する専用のUITableViewを作成し、それを画面の上からかぶせる形で描画します。 このTableViewの実装をUITableViewにかぶせることで、あたかもUITableViewの内容が検索結果に置き換わったかのように見せることが可能になるわけです。

検索機能は、背後で動作するshouldReloadTableForSearchScopeや、shouldReloadTableForSearchScopeなどで実現しつつ、UITableViewのcellForRowAtIndexPathや、numberOfRowCountなどのメソッドを適切に通常時と記述し分けることで実装を行います。もちろん、この他のメソッドについても必要に応じて書き換える必要があり、たとえばSectionIndexを実装している場合にはsectionIndexTitlesForTableViewなどを変更する必要があるでしょう。

ところで、検索結果表示用のTableViewは検索結果を表示するときだけ生成され、検索結果が非表示になるとすぐに破棄されます。 この性質上、たとえばdequeReusableCellWithIdentifierを実行するときのようにメモリの再利用をしようとするときや、rowHeightを変更する場合のようにデザインを反映させようとする場合には注意が必要です。

参考