LoginSignup
35
36

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-08-10

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

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

コード全体はGitHubにあげてます。記事よりもGitHubのコードの方が新しいです。->https://github.com/on0z/SearchControllerSample

前提

  • 主にコードベース。たしかUISearchControllerはStoryboardでは設定できなかったと思う。
  • メインのViewはUITableViewControllerを継承したMasterViewController。(多分他を継承してても変わらない。)
  • 検索結果を別のViewで表示する。UITableViewControllerを継承したSearchResultViewControllerとする。
  • 遷移先としてDetailViewControllerを用意する。
  • UINavigationControllerのpushViewController()で遷移する。コードで遷移する。もちろんnavigationBarがある。
  • MasterからもSearchResultからもDetailに遷移できるようにする。<-ここで詰まる。今回のメイン

スクリーンショット 2017-09-25 18.20.28.png

やり方

基本

Navigation

ここを参考にするか、Storyboardを使うかしてNavigationController(ルートビューはMasterViewController)を配置する。

MasterViewController

メインのViewではUISearchControllerを設定して、SearchResultViewControllerとのリンクを行う。

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

ここでは、検索がされた時の動作と、その結果の表示を設定する。

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。ちゃんと遷移できたか確認できるように表示を設定する。ここはお好きなようにどうぞ。

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の中とかに追記してはダメ。

//Protocols
protocol SelectedCellProtocol: class {
    func didSelectedCell(view: DetailViewController)
}

MasterViewController

ResultからMasterを通ってDetailに遷移できるようにする。

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に遷移する。

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

MasterViewController

delegateとplaceholderとスコープボタンの設定

class MasterViewController: UITableViewController, SelectedCellProtocol {

    ・・・

    override func viewDidLoad() {

        ・・・

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

        ・・・

    }

    ・・・

}

SearchResultViewController

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

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タッチを有効化させるために

if #available(iOS 9.0, *) {
    if self.traitCollection.forceTouchCapability == UIForceTouchCapability.available {
        registerForPreviewing(with: self, sourceView: view)
    }
}

とかをviewDidLoad()に書いたりすると思いますが、この状態ではself.traitCollection.forceTouchCapabilityの返り値は.unknownです。
MasterViewControllerでの値を持って来ましょう。

iOS11でSearchBarのカスタマイズ

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

35
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
36