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に遷移できるようにする。<-ここで詰まる。今回のメイン
#やり方
##基本
###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