UISearchControllerの使い方。主に結果画面からNavigationControllerでPush

iOS8から実装されたUISearchControllerの使い方が全然わからなかったのでメモ。日本語の記事も少なかった気がする。
特に、検索結果画面からNavigationでPushする方法がわからなかった。今回はそれが本題。

この記事は「`searchResultUpdater = self;`とか`searchResultController = self;`はできたけど`self`じゃなくて別のViewではどうするの？」がミソです。それはもうできてる人に見てもらいたいです。

コード全体はGitHubにあげてます。記事よりもGitHubのコードの方が新しいです。->https://github.com/on0z/SearchControllerSample
#前提
- 主にコードベース。たしかUISearchControllerはStoryboardでは設定できなかったと思う。
- メインのViewはUITableViewControllerを継承したMasterViewController。(多分他を継承してても変わらない。)
- 検索結果を<Font color="red">別のView</Font>で表示する。UITableViewControllerを継承したSearchResultViewControllerとする。
- 遷移先としてDetailViewControllerを用意する。
- UINavigationControllerのpushViewController()で遷移する。コードで遷移する。もちろんnavigationBarがある。
- MasterからもSearchResultからもDetailに遷移できるようにする。<-ここで詰まる。今回のメイン

<img width="1280" alt="スクリーンショット 2017-09-25 18.20.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/83914/c5871788-b922-d93d-ccbc-9cda21951c0b.png">

#やり方
##基本
###Navigation
[ここ](http://qiita.com/mochizukikotaro/items/f053495eb130e92e13e8)を参考にするか、Storyboardを使うかしてNavigationController(ルートビューはMasterViewController)を配置する。
###MasterViewController
メインのViewではUISearchControllerを設定して、SearchResultViewControllerとのリンクを行う。

```swift
import UIKit

class MasterViewController: UITableViewController {
    
    //データを定義
    let data: [String] = ["This is a test sentence",
                          "Please add your sentence.",
                          "I like Apple",
                          "Do you like an apple?",
                          "今日はいい天気ですね",
                          "はい、いい 天気です",
                          "私はAppleが好きです",
                          "私はリンゴが嫌いです",
                          "Apple社の天気は雷",
                          "Apple社の天気は雷?"]
    
    //UISearchControllerの変数を作成
    var mySearchController: UISearchController!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "Master"

        //セルの名前を設定
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        
        //検索に関する設定
        let resultController = SearchResultViewController()
        mySearchController = UISearchController(searchResultsController: resultController) //検索結果を表示するViewを設定
        mySearchController.hidesNavigationBarDuringPresentation = true//検索バーを押したらナビゲーションバーが隠れる
        mySearchController.dimsBackgroundDuringPresentation = true//検索中は後ろが暗くなる。
        self.definesPresentationContext = true
        let searchBar = mySearchController.searchBar
        searchBar.searchBarStyle = .default //検索バーのスタイル なくてもいい
        searchBar.barStyle = .default //バーのスタイル なくてもいい
        if #available(iOS 11.0, *) {
            self.navigationItem.searchController = mySearchController //iOS11からは、NavigationItemにSearchControllerを設定します。
        } else {
            self.tableView.tableHeaderView = searchBar //TableViewの一番上にsearchBarを設置
        }
        searchBar.sizeToFit()
        mySearchController.searchResultsUpdater = resultController //検索されると動くViewを設定
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        //セルに表示するテキストを設定
        cell.textLabel?.text = data[indexPath.row]

        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let DetailView = DetailViewController()
        DetailView.text = data[indexPath.row]
        self.navigationController?.pushViewController(DetailView, animated: true)
    }
}
```
###SearchResultViewController
ここでは、検索がされた時の動作と、その結果の表示を設定する。

```swift
import UIKit

class SearchResultViewController: UITableViewController, UISearchResultsUpdating { //UISearchResultsUpdatingを継承する
    
    //検索にひっかっかったものを入れる変数
    var filteredItems: [String] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //セルの名前を設定
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return filteredItems.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        
        //セルに表示するテキストを設定
        cell.textLabel?.text = filteredItems[indexPath.row]
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let DetailView = DetailViewController()
        DetailView.text = self.filteredItems[indexPath.row]
        self.navigationController?.pushViewController(DetailView, animated: true)
    }
    
    //検索された時に実行される関数
    func updateSearchResults(for searchController: UISearchController) {
        if let text = searchController.searchBar.text{
            filteredItems = MasterViewController().data.filter{$0.localizedCaseInsensitiveContains(text)}
            tableView.reloadData()
        }
    }
}
```
###DetailViewController
遷移先となるView。ちゃんと遷移できたか確認できるように表示を設定する。ここはお好きなようにどうぞ。

```swift
import UIKit

class DetailViewController: UIViewController {
    
    //選択された要素を入れる。表示にも使い回す。
    var text = "none"

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = UIColor.white//背景を白色にする
        self.title = text//タイトルを選択された文字列にする
        let label: UILabel = UILabel()//ラベルを作る
        label.text = text//ラベルのテキストを選択された文字列にする
        label.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: 50)//ラベルの大きさを設定する
        label.center = self.view.center//ラベルの位置を真ん中にする
        label.textAlignment = .center//テキストを中央揃えにする
        self.view.addSubview(label)//ラベルを表示する
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}
```
##遷移の設定
ここまでで見た目は確定する。検索もできる。だが、検索結果のセルをタップしても遷移しない。
なぜなら、ResultのnavigationControllerはnilだから。self.navigationController?.pushViewController()は動作しない。
つまり、Masterに遷移処理を委託しないといけない。
###これを追記
これを追記する。なんかのclassの中とかに追記してはダメ。

```swift
//Protocols
protocol SelectedCellProtocol: class {
    func didSelectedCell(view: DetailViewController)
}
```
###MasterViewController
ResultからMasterを通ってDetailに遷移できるようにする。

```swift
class MasterViewController: UITableViewController, SelectedCellProtocol/*これを追記*/ {

    ・・・

    override func viewDidLoad(){

        ・・・

        let resultController = SearchResultController()
        resultController.delegate = self //これを追記

        ・・・

    }
    
    //下の3行を追記
    func didSelectedCell(view: DetailViewController) {
        self.navigationController?.pushViewController(view, animated: true)
    }

    ・・・
}
```
###SearchResultController
ResultからMasterを通ってDetailに遷移する。

```swift
class SearchResultViewController: UITableViewController, UISearchResultsUpdating { //UISearchResultsUpdatingを継承する
    
    //検索にひっかっかったものを入れる変数
    var filteredItems: [String] = []
    weak var delegate = SelectedCellProtocol? //これを追記
    
    ・・・
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let DetailView = DetailViewController()
        DetailView.text = self.filteredItems[indexPath.row]
        self.delegate?.didSelectedCell(view: DetailView)
    }

    ・・・
}
```

完成

#UISearchControllerのその他の使い方
書くことを見つければ追記します。
##スコープを設定する。AndOr検索をつける。
AndOr検索については別記事にしてます。->[配列の中を検索 - Qiita](http://qiita.com/on0z/items/609da35821aa06a317dd)
###MasterViewController
delegateとplaceholderとスコープボタンの設定

```swift
class MasterViewController: UITableViewController, SelectedCellProtocol {
    
    ・・・

    override func viewDidLoad() {
        
        ・・・

        let searchBar = mySearchController.searchBar
        //以下3行追記
        searchBar.delegate = resultController//スコープボタンを切り替えた時のために必要
        searchBar.placeholder = "空白区切りで検索" //placeholderを設定
        searchBar.scopeButtonTitles = ["どれか含む", "全て含む"] //スコープボタンのタイトルを設定

        ・・・

    }

    ・・・

}
```
###SearchResultViewController
AndOr検索とスコープボタンを押した時の動作の設定

```swift
class SearchResultViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate/*<-これを追記*/ {

    ・・・

    //以下の関数を追記
    //スコープボタンが押されてインデックスが変わった時に呼ばれる。
    func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
        guard let text = searchBar.text else{ return }
        if selectedScope == 0{//or
            filteredItems = MasterViewController().data.filter{
                for word in text.components(separatedBy: " "){
                    if $0.localizedCaseInsensitiveContains(word){
                        return true
                    }
                }
                return false
            }
        }else if selectedScope == 1{//and
            filteredItems = MasterViewController().data.filter{
                for word in text.components(separatedBy: " "){
                    if word == ""{
                        continue
                    }
                    if !$0.localizedCaseInsensitiveContains(word){
                        return false
                    }
                }
                return true
            }
        }
        tableView.reloadData()
    }

    ・・・

    //検索された時に実行される関数
    //中を書き換え
    func updateSearchResults(for searchController: UISearchController) {
        guard let text = searchController.searchBar.text else{ return }
        if searchController.searchBar.selectedScopeButtonIndex == 0{//or
            filteredItems = MasterViewController().data.filter{
                for word in text.components(separatedBy: " "){
                    if $0.localizedCaseInsensitiveContains(word){
                        return true
                    }
                }
                return false
            }
        }else if searchController.searchBar.selectedScopeButtonIndex == 1{//and
            filteredItems = MasterViewController().data.filter{
                for word in text.components(separatedBy: " "){
                    if word == ""{
                        continue
                    }
                    if !$0.localizedCaseInsensitiveContains(word){
                        return false
                    }
                }
                return true
            }
        }
        tableView.reloadData()
    }

}
```

##検索結果画面で3Dタッチを有効にする
3Dタッチを有効化させるために

```swift
if #available(iOS 9.0, *) {
    if self.traitCollection.forceTouchCapability == UIForceTouchCapability.available {
        registerForPreviewing(with: self, sourceView: view)
    }
}
```
とかを`viewDidLoad()`に書いたりすると思いますが、この状態では`self.traitCollection.forceTouchCapability`の返り値は`.unknown`です。
MasterViewControllerでの値を持って来ましょう。

##iOS11でSearchBarのカスタマイズ

```swift
if let searchTextField: UITextField = searchBar.value(forKey: "searchField") as? UITextField {
    //
    //iOS10以前でのカスタマイズ
    //

    if #available(iOS 11.0, *){
        if let backgroundview = searchTextField.subviews.first {
            // Background color
            backgroundview.backgroundColor = UIColor.white
                    
            // Rounded corner
            backgroundview.layer.cornerRadius = 10;
            backgroundview.clipsToBounds = true;
        }
    }
}
```
参考サイト:https://tutel.me/c/programming/questions/45997996/ios+11+uisearchbar+in+uinavigationbar

#コメント
GitHubにあげました。->https://github.com/on0z/SearchControllerSample
searchBarをおしてactiveになったときに、ステータスバースタイルをlightContentsにする方法がわからないので、だれか教えてください。
↑5/7 iOS10.3.1にていつの間にか解決してました。

#参考サイト
参考にした神リンク http://stackoverflow.com/questions/35178691/swift-search-result-controller-in-search-results-segue-to-another-view-controlle