[iOS 10] [Core Spotlight] CSSearchQuery でインデックス済みのコンテンツを検索する
Core Spotlight
iOS 9 から Core Spotlight フレームワークが追加され、Spotlight の検索候補としてアプリ内コンテンツが登録できるようになりました。この機能を実装することにより、アプリ内の特定のコンテンツに簡単にアクセスするための入り口を提供することができます。
iOS 10 では CSSearchQuery というクラスが追加されました。このクラスを使うと、アプリ内でユーザーが使ったことのあるコンテンツを検索することができます。
今回は CSSearchQuery を使って、インデックス済みのコンテンツを検索する実装を試してみたいと思います。
サンプルプロジェクトの作成とビューの追加
まずは適当な Xcode プロジェクトを作成します。今回は「CoreSpotlightSample」としました。
デフォルトで作成される Main.storyboard
の初期表示のビューコントローラにビューを追加します。以下の2種類のビューを追加します。
- タップすると適当なコンテンツをインデックスに追加するボタン (Stack View などを使って複数配置する)
- コンテンツの検索結果を表示するテーブルビュー
また ViewController
クラス内で CoreSpotlight フレームワークをインポートしておきます。
import CoreSpotlight
コンテンツをインデックスに追加する
各ボタンを IBAction で ViewController
クラスと接続します。これらのボタンをタップすると、異なるコンテンツがインデックスに追加されるようにします。
次のようなメソッドを、ViewController
クラスに実装します。
// コンテンツをインデックスに追加するメソッド func insert(_ identifier: String, name: String) { let attrs : CSSearchableItemAttributeSet = CSSearchableItemAttributeSet(itemContentType: "ppap") attrs.displayName = name let item = CSSearchableItem(uniqueIdentifier:identifier, domainIdentifier: "jp.classmethod.ppap", attributeSet: attrs) CSSearchableIndex.default().indexSearchableItems([item]) { error in if let e = error { print("\(e)") } } } @IBAction func penButtonDidTap(_ sender: UIButton) { insert("pen", name: "ペン") } @IBAction func appleButtonDidTap(_ sender: UIButton) { insert("apple", name: "アップル") } @IBAction func applePenButtonDidTap(_ sender: UIButton) { insert("apple_pen", name: "アッポーペン") } @IBAction func pineappleButtonDidTap(_ sender: UIButton) { insert("pineapple", name: "パイナップル") } @IBAction func pineapplePenButtonDidTap(_ sender: UIButton) { insert("pineapple_pen", name: "パイナッポーペン") } @IBAction func penPineappleApplePenButtonDidTap(_ sender: UIButton) { insert("pen_pineapple_apple_pen", name: "ペンパイナッポーアッポーペン") }
ここまででアプリを実行し、ボタンを適当にタップしてください。
すると Spotlight を開くとコンテンツが出てくるようになります。
CSSearchQuery で検索する
次にいよいよ検索です。まずはテーブルビューを IBOutlet で接続し、CSSearchQuery
と 検索結果の格納用の配列をプロパティとして定義しておきます。
var items: [CSSearchableItem]? var query: CSSearchQuery! @IBOutlet weak var tableView: UITableView!
次に、クエリを実際に実行するメソッドを書きます。
func startQuery() { let queryString = "displayName == '*ペン*'cwdt" let attributes = ["displayName", "identifier"] query = CSSearchQuery(queryString: queryString, attributes: attributes) query.foundItemsHandler = { items in print("found") self.items = items } query.completionHandler = { error in if let e = error { print(e) } print("complete") DispatchQueue.main.async { self.tableView.reloadData() } } query.start() }
CSSearchQuery
をインスタンス化するときに、イニシャライザ引数で queryString
と attributes
を渡します。queryString
は、ドキュメントにある記法を参考に書きます。ここでは ペン
が部分一致するクエリにしています。
attributes
は、検索結果 (CSSearchableItem
) に含めるプロパティです。検索結果では、ここで指定しているプロパティの値が取得できます。
foundItemsHandler
は検索がヒットしたときに呼ばれる関数、completionHandler
は検索が完了したときに呼ばれる関数です。
最後に start()
を呼び出して実行します。
UITableView への反映
検索結果は UITableView に反映されるようにしましょう。ここは、特に真新しい事はありません。
import UIKit import CoreSpotlight class ViewController: UIViewController { var items: [CSSearchableItem]? var query: CSSearchQuery! @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self startQuery() } // ...省略 } extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) if let item = items?[indexPath.row] { cell.textLabel?.text = item.attributeSet.displayName cell.detailTextLabel?.text = item.uniqueIdentifier } return cell } }
また、タップ後にすぐに検索結果が反映されるよう、insert(_:name:)
の中で実行している indexSearchableItems(_:completionHandler:)
のハンドラ内で startQuery()
が呼び出されるようにしておきましょう。
func startQuery() { let queryString = "displayName == '*ペン*'cwdt" let attributes = ["displayName", "identifier"] query = CSSearchQuery(queryString: queryString, attributes: attributes) query.foundItemsHandler = { items in print("found") self.items = items } query.completionHandler = { error in if let e = error { print(e) } print("complete") DispatchQueue.main.async { self.tableView.reloadData() } } query.start() }
実行する
それではデバッグしてみましょう。ボタンをタップすると、テーブルビューに「ペン」が部分的に含まれているコンテンツだけ表示されます。
まとめ
Spotlight のエンジンを使って、コンテンツを簡単に、高速に検索することができます。ユーザーに、使ったことのあるコンテンツにアクセスしやすくなるような機能が提供できるはずです。ぜひ活用していきましょう。