[iOS 10] [Core Spotlight] CSSearchQuery でインデックス済みのコンテンツを検索する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Core Spotlight

iOS 9 から Core Spotlight フレームワークが追加され、Spotlight の検索候補としてアプリ内コンテンツが登録できるようになりました。この機能を実装することにより、アプリ内の特定のコンテンツに簡単にアクセスするための入り口を提供することができます。

iOS 10 では CSSearchQuery というクラスが追加されました。このクラスを使うと、アプリ内でユーザーが使ったことのあるコンテンツを検索することができます。

今回は CSSearchQuery を使って、インデックス済みのコンテンツを検索する実装を試してみたいと思います。

サンプルプロジェクトの作成とビューの追加

まずは適当な Xcode プロジェクトを作成します。今回は「CoreSpotlightSample」としました。

デフォルトで作成される Main.storyboard の初期表示のビューコントローラにビューを追加します。以下の2種類のビューを追加します。

  1. タップすると適当なコンテンツをインデックスに追加するボタン (Stack View などを使って複数配置する)
  2. コンテンツの検索結果を表示するテーブルビュー

cs-search-query-01

また 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: "ペンパイナッポーアッポーペン")
}

ここまででアプリを実行し、ボタンを適当にタップしてください。

cs-search-query-02

すると Spotlight を開くとコンテンツが出てくるようになります。

cs-search-query-03

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 をインスタンス化するときに、イニシャライザ引数で queryStringattributes を渡します。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()
}

実行する

それではデバッグしてみましょう。ボタンをタップすると、テーブルビューに「ペン」が部分的に含まれているコンテンツだけ表示されます。

cs-search-query-04

まとめ

Spotlight のエンジンを使って、コンテンツを簡単に、高速に検索することができます。ユーザーに、使ったことのあるコンテンツにアクセスしやすくなるような機能が提供できるはずです。ぜひ活用していきましょう。