UISearchControllerチュートリアル-開始
Uisearchcontroller Tutorial Start
元の: UISearchControllerチュートリアル:はじめに
の: トム・エリオット
翻訳者: kmyhy更新メモ:このチュートリアルは、TomElliottによってXcode9ベータ、Swift 4、およびiOS11にアップグレードされています。元のチュートリアルの作者はAndyPereiraです。
ユーザーにとって、大量のデータのリストをスクロールすることは、遅くて苦痛なことです。大量のデータを処理する場合、ユーザーが特定のコンテンツを見つけられるようにすることが重要です。 UIKitにはUISearchBarが含まれています。UISearchBarはUITableViewにシームレスに接続されているため、ユーザーは高速で応答性の高い情報フィルタリングを実行できます。
このUISearchControllerチュートリアルでは、標準のテーブルビューに基づいて検索機能を実装するCandyアプリを作成します。 UISearchControllerを使用して、テーブルビューの検索機能を提供し、動的フィルタリングをサポートし、オプションのスコープバーを使用します。最後に、アプリをよりユーザーフレンドリーにし、ユーザーの緊急のニーズに対応します。
準備はできたか?それでは始めましょう。
開始
から ここに 開始プロジェクトをダウンロードして、プロジェクトを開きます。このアプリにはすでにナビゲーションコントローラーが含まれています。 Xcodeプロジェクトナビゲータで、プロジェクトファイルCandySearchを選択し、次にターゲットでCandySearchを選択し、署名列でチームを開発チームに変更します。ビルドして実行すると、空のリストが表示されます。
Xcodeに戻ると、Candy.swiftで構造が定義され、各キャンディーの表示情報が格納されます。この構造には、カテゴリと名前の2つの属性があります。
ユーザーがアプリで特定のキャンディーを検索すると、検索文字列が名前属性と一致するために使用されます。このチュートリアルの後半でスコープバーを実装するときに、category属性を使用します。
テーブルビューをレンダリング
MasterViewController.swiftを開きます。 candies属性は、さまざまなCandyオブジェクトを管理するために使用され、ユーザーはこれらのオブジェクトを検索します。そうは言っても、まだキャンディーは追加していません!
このチュートリアルでは、検索バーが実際のアプリである場合、検索バーの使用法を示すために固定数のキャンディーを作成するだけで済みます。何千ものオブジェクトの中から検索する必要がある場合があります。ただし、アプリが検索するオブジェクトの数に関係なく、方法は同じです。この方法の最大の利点はスケーラビリティです。
キャンディ配列を埋めるには、viewDidLoad()メソッドのsuper.viewDidLoad()文の後に次を追加します。
candies = [ Candy(category:'Chocolate', name:'Chocolate Bar'), Candy(category:'Chocolate', name:'Chocolate Chip'), Candy(category:'Chocolate', name:'Dark Chocolate'), Candy(category:'Hard', name:'Lollipop'), Candy(category:'Hard', name:'Candy Cane'), Candy(category:'Hard', name:'Jaw Breaker'), Candy(category:'Other', name:'Caramel'), Candy(category:'Other', name:'Sour Chew'), Candy(category:'Other', name:'Gummi Bear'), Candy(category:'Other', name:'Candy Floss'), Candy(category:'Chocolate', name:'Chocolate Coin'), Candy(category:'Chocolate', name:'Chocolate Egg'), Candy(category:'Other', name:'Jelly Beans'), Candy(category:'Other', name:'Liquorice'), Candy(category:'Hard', name:'Toffee Apple') ]
ビルドして実行します。テーブルビューのdelegateメソッドとdataSourceメソッドはすでに記述されているため、テーブルビューはすでにリストを表示できることがわかります。
行をクリックすると、キャンディーの詳細ページが表示されます。
たくさんの砂糖!欲しいキャンディーを見つけるのに少し手間がかかります! UISearchBarを使用する必要があります。
UISearchControllerの紹介
UISearchControllerのドキュメントを見ると、ドキュメントがほとんどないことがわかります。検索関連の機能は一切実装していません。このクラスは、iOSアプリで実装する必要がある標準インターフェースのみをユーザーに提供します。
UISearchControllerはデリゲートプロトコルを処理し、アプリの他の部分はユーザーが何をしているかを理解します。文字列照合の実際の機能を自分で実装する必要があります。
これは少し怖いように思えるかもしれませんが、カスタマイズされた検索機能を使用すると、アプリが特定の結果セットを返すかどうかをしっかりと制御できます。ユーザーは常に検索がインテリジェントで高速であることを望んでいます。
テーブルビューの検索機能を使用したことがある場合は、おそらくUISearchDisplayControllerをご存知でしょう。 iOS 8以降、このクラスは廃止され、UISearchControllerに置き換えられました。これにより、検索プロセスが簡素化されます。
残念ながら、この記事の執筆時点では、IBはUISearchControllerをサポートしていません。したがって、コード化された方法でUIを作成する必要があります。
MasterViewController.swiftで、キャンディー配列の定義の下に新しいプロパティを追加します。
let searchController = UISearchController(searchResultsController: nil)
searchResultsControllerの初期値はnilとして指定されます。これは、検索結果を表示するために探しているテーブルビューが必要であることを検索コントローラーに通知します。ここで別のビューコントローラーを指定すると、そのビューコントローラーが結果の表示に使用されます。
MasterViewControllerが検索バーにリアルタイムで応答できるようにするには、UISearchResultsUpdatingを実装する必要があります。このプロトコルは、ユーザー入力に基づいて検索結果を動的に変更する方法を定義します。
まだMasterViewController.swiftに、拡張機能を追加します。
extension MasterViewController: UISearchResultsUpdating { // MARK: - UISearchResultsUpdating Delegate func updateSearchResults(for searchController: UISearchController) { // TODO } }
updateSearchResults(for :)メソッドは、クラスが実装する必要がある唯一のUISearchResultsUpdatingプロトコルメソッドです。このメソッドは後で入力します。
次に、searchControllerにいくつかのプロパティを設定する必要があります。まだMasterViewController.swiftで、viewDidLoad()メソッドのsuper.viewDidLoad()の後に追加します。
// Setup the Search Controller searchController.searchResultsUpdater = self searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.placeholder = 'Search Candies' navigationItem.searchController = searchController definesPresentationContext = true
コードは次のように説明されています。
- searchResultsUpdaterは、新しいUISearchResultsUpdatingプロトコルを実装するUISearchControllerのプロパティです。このプロトコルにより、独自のクラスがUISearchBarテキストの変更の通知を受信できるようになります。
- デフォルトでは、UISearchControllerは半透明のマスクとして表示されます。別のビューコントローラをsearchResultsControllerとして使用する場合は、このプロパティを使用します。ここでは、現在のビューを使用して結果を表示するため、マスクレイヤーは必要ありません。
- searchBarのプレースホルダーテキストを設定します。
- iOS11の新機能 、searchBarをNavigationItemに追加します。これは、IBとUISearchController間の互換性のために必要です。
5.最後に、View ControllerのdefinesPresentationContextをtrueに設定して、ユーザーがUISearchControllerから別のViewControllerにジャンプしたときに検索バーが表示されないようにします。
UISearchResults更新とフィルタリング
検索コントローラーを作成したら、それを機能させるためにいくつかのコードを作成する必要があります。まず、MasterViewControllerの上部にプロパティを追加します。
var filteredCandies = [Candy]()
この属性は、ユーザーが見つけたキャンディーを保存するために使用されます。
次に、ヘルパーメソッドをメインのMasterViewControllerクラスに追加します。
// MARK: - Private instance methods func searchBarIsEmpty() -> Bool { // Returns true if the text is empty or nil return searchController.searchBar.text?.isEmpty ?? true } func filterContentForSearchText(_ searchText: String, scope: String = 'All') { filteredCandies = candies.filter({( candy : Candy) -> Bool in return candy.name.lowercased().contains(searchText.lowercased()) }) tableView.reloadData() }
searchBarIsEmpty()は説明を必要としません。
filterContentForSearchText(_:scope :)メソッドは、searchTextに基づいてキャンディーをフィルター処理し、その結果をfilteredCandies配列に配置します。 scope:パラメーターに関係なく、後で使用されます。
filter()関数にはクロージャパラメーターが必要であり、クロージャタイプは(candy:Candy)-> Boolです。配列内の要素をトラバースしてから、各要素をクロージャーに渡し、クロージャーを呼び出します。
このクロージャを使用して、特定のキャンディーを検索結果の一部にしてユーザーに表示するかどうかを決定できます。このキャンディーをfilteredCandies配列に配置する必要がある場合は、trueを返し、そうでない場合はfalseを返します。
返す必要があるかどうかを判断するには、contains(_ :)メソッドを使用して、キャンディーの名前がsearchTextと一致するかどうかを確認できます。比較する前に、両方の文字列を小文字に変換する必要があります。
注:ほとんどの場合、ユーザーは検索時に文字が大文字であるか小文字であるかは関係ありません。したがって、入力テキストを小文字に変換し、それをキャンディー名の小文字と比較するだけで、大文字と小文字を区別しない検索を実行できます。 。これで、チョコレートを出力することもチョコレートを入力することもでき、同じ結果を得ることができます。これはかなり良いですよね?
UISearchResultsUpdatingプロトコルを覚えていますか? updateSearchResults(for :)メソッドにはTODOコメントが1つだけあります。これで、検索結果を更新するときにこのメソッドを直接呼び出すことができるように、メソッドを作成しました。
updateSearchResults(for :)メソッドのTODOコメントをfilterContentForSearchText(_:scope :)の呼び出しに変更します。
filterContentForSearchText(searchController.searchBar.text!)
これで、ユーザーが検索バーの各文字を削除または入力すると、UISearchControllerはupdateSearchResults(for :)メソッドを呼び出してMasterViewControllerクラスに通知し、同時にfilterContentForSearchText(_:scope :)を呼び出します。
ビルドして実行すると、フォームbでリリースされた追加の検索バーが表示されます。
ただし、一部の検索キーワードを入力しても、検索結果は表示されません。どうして?まだコードを追加していないので、これらの検索結果をいつ使用するかをテーブルビューに知らせてください。
テーブルビューを更新
MasterViewController.swiftに戻り、現在検索しているかどうかを判断するメソッドを追加します。
func isFiltering() -> Bool { return searchController.isActive && !searchBarIsEmpty() }
次に、tableView(_:numberOfRowsInSection :)メソッドを変更します。
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if isFiltering() { return filteredCandies.count } return candies.count }
多くの変更はありません。ユーザーが検索しているかどうかを確認し、状況に応じて、filteredCandiesまたはcandies配列をテーブルデータソースとして使用するだけです。
次に、tableView(_:cellForRowAt :)メソッドを次のように変更します。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: 'Cell', for: indexPath) let candy: Candy if isFiltering() { candy = filteredCandies[indexPath.row] } else { candy = candies[indexPath.row] } cell.textLabel!.text = candy.name cell.detailTextLabel!.text = candy.category return cell }
どちらのメソッドもisFiltering()を呼び出します。このメソッドは、searchControllerのアクティブなプロパティを判断して、表示する配列を決定します。
ユーザーが検索バーの検索バーをクリックすると、アクティブな属性がtrueに設定されます。検索コントローラーのアクティブプロパティがtrueで、ユーザー入力が空でない場合、filteredCandies配列のデータが使用されます。それ以外の場合は、完全なキャンディデータが使用されます。
この場合も、検索コントローラーは結果テーブルビューの表示と非表示を自動的に処理するため、コントローラーの状態とユーザーが結果を検索するかどうかに基づいて、正しいデータ(フィルター処理された配列または完全な配列)を提供するだけで済みます。
ビルドして実行します。テーブルの行をフィルタリングできる検索バーがすでにあります。
このアプリを使用して、あらゆる種類のキャンディーを見つける方法を確認してください。まだいくつかの問題があります。検索結果から行を選択すると、詳細ページに他のキャンディーが表示されます。それを解決しましょう。
詳細ページにデータを渡す
詳細ページにデータを渡すときは、このView Controllerに、ユーザーがどの環境にいるか(完全なリストまたは検索結果)を通知する必要があります。
引き続きMasterViewController.swiftのprepare(for:sender :)メソッドで、次のコードを見つけます。
let candy = candies[indexPath.row]
次のように置き換えます。
let candy: Candy if isFiltering() { candy = filteredCandies[indexPath.row] } else { candy = candies[indexPath.row] }
ここでは、最初にisFiltering()を使用して、詳細ビューコントローラーのセグエを実行するときに正しいキャンディオブジェクトを判断して提供します。
ビルドして実行し、これらのコードを実行して、アプリがメインリストまたは検索リストから詳細ページに正しく移動できるかどうかを確認します。
スコープバーで結果セットをフィルタリングする
結果セットをフィルタリングする別の方法をユーザーに提供する場合は、検索バーにスコープバーを添付して、カテゴリに基づいてキャンディーをフィルタリングできます。キャンディー配列を作成すると、キャンディーオブジェクトのさまざまなカテゴリ(チョコレート、ハードキャンディーなど)に従ってフィルター処理されます。
まず、MasterViewControllerでスコープバーを作成する必要があります。スコープバーは、検索スコープを特定のスコープに絞り込むために使用されるセグメント化されたコントロールです。特定のスコープはユーザーが定義できます。この例では、パスのタイプを指します。もちろん、モデル、範囲、またはその他のものなど、他のものにすることもできます。
スコープバーの使用は非常に簡単で、デリゲートメソッドを実装するだけで済みます。
MasterViewController.swiftで、UISearchBarDelegateを実装するための別の拡張機能を追加します。 UISearchResultsUpdating拡張機能の後に追加:
extension MasterViewController: UISearchBarDelegate { // MARK: - UISearchBar Delegate func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope]) } }
このデリゲートメソッドは、ユーザーがスコープバーボタンを切り替えると呼び出されます。このとき、もう一度フィルタリングすることをお勧めします。そのため、新しいスコープ値を使用してfilterContentForSearchText(_:scope :)メソッドを呼び出します。
次に、filterContentForSearchText(_:scope :)メソッドを変更して、スコープパラメーターを使用します。
func filterContentForSearchText(_ searchText: String, scope: String = 'All') { filteredCandies = candies.filter({( candy : Candy) -> Bool in let doesCategoryMatch = (scope == 'All') || (candy.category == scope) if searchBarIsEmpty() { return doesCategoryMatch } else { return doesCategoryMatch && candy.name.lowercased().contains(searchText.lowercased()) } }) tableView.reloadData() }
ここでは、キャンディーのカテゴリとスコープバーによって渡されたスコープが一致します(またはスコープがすべてであるかどうか)。次に、コンテンツが検索バーに入力されているかどうかを確認してから、キャンディーをフィルタリングします。また、スコープバーが選択されたときにtrueを返すようにisFiltering()メソッドを変更する必要があります。
func isFiltering() -> Bool
これでほぼ完了ですが、スコープフィルタリングメカニズムはまだ完了していません。現在選択されているスコープを渡すことができるように、ファーストクラス拡張のupdateSearchResults(for :)メソッドを変更する必要があります。
func updateSearchResults(for searchController: UISearchController) { let searchBar = searchController.searchBar let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex] filterContentForSearchText(searchController.searchBar.text!, scope: scope) }
もう1つの問題は、ユーザーがスコープバーをまだ表示できないことです。スコープバーを追加するには、最初に検索コントローラーが最初に作成されたときのコードを見つけます。 MasterViewController.swiftのviewDidLoad()メソッドで、キャンディーを割り当てる前に追加します。
// Setup the Scope Bar searchController.searchBar.scopeButtonTitles = ['All', 'Chocolate', 'Hard', 'Other'] searchController.searchBar.delegate = self
これにより、キャンディのカテゴリがスコープバーのタイトルとして検索バーにスコープバーが追加されます。また、検索時にキャンディーカテゴリを無視するために「すべて」を追加しました。
これで、入力すると、選択したスコープボタンと検索テキストが同時に機能します。
ビルドして実行します。検索範囲を変更するには、数文字を入力します。
キャラメルを入力し、範囲として[すべて]を選択します。結果はリストに表示されますが、チョコレートに絞り込むと、チョコレートではないのでキャラメルが消えてしまいます!
小さな問題があります。結果セットの数は示していません。これは、結果セットがまったく返されない場合に特に重要です。検索結果がないかどうかをユーザーが理解するのが難しいか、インターネットの速度が遅すぎて待つことができないためです。
結果カウント表示を追加
この問題を解決するには、ビューにフッターを追加する必要があります。キャンディーリストをフィルタリングしているときに表示され、検索結果の配列にキャンディーがいくつあるかをユーザーに知らせます。 SearchFooter.swiftを開きます。単純なUIViewには、検索結果セットの数を示すためのラベルとパブリックメソッドが既に含まれています。
MasterViewController.swiftに戻ります。クラスの先頭では、このフッターのIBOutletはすでに準備ができており、Main.storyboardのマスターシーンの下部に表示されます。 viewDidLoad()メソッドがスコープバーを作成した後に追加します。
// Setup the search footer tableView.tableFooterView = searchFooter
この文は、カスタムフッターをテーブルビューのフッタービューに設定します。次に、検索テキストが変更されたら、その結果セット番号を変更します。 tableView(_:numberOfRowsInSection :)を次のように変更します。
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if isFiltering() { searchFooter.setIsFilteringToShow(filteredItemCount: filteredCandies.count, of: candies.count) return filteredCandies.count } searchFooter.setNotFiltering() return candies.count }
これは、searchFooterメソッドを呼び出すだけです。
ビルドして実行し、いくつかの検索を実行し、検索結果が戻ったときにフッターの変更を観察します。キーボードを非表示にしてこのフッターを表示するには、検索ボタンをクリックする必要があります。
次は何ですか?
おめでとうございます-アプリはすでにメインテーブルビューで直接検索できます。
このチュートリアルのサンプルプロジェクトは ここに ダウンロード。
テーブルビューはすべてのアプリで使用されており、検索オプションを提供することをお勧めします。 UISearchBarとUISearchControllerを使用すると、iOSはすぐに使用できる多くの機能を提供するため、使用しない理由はありません。大きなテーブルで検索できることも、優れたユーザーエクスペリエンスです。あなたがそれらを提供していないことにユーザーが気付いた場合、彼らは非常に不幸になります。
今後、テーブルビューアプリに検索機能を追加していただきたいと思います。質問やコメントがある場合は、フォーラムにご相談ください。