便利な解錠方法-Spotlight解錠

WebエンジニアのBunです。主にiOSアプリの開発を担当しています。

日々いろんな解錠方法について考えています。特にiOSアプリで使えるいろんな便利な機能を使った解錠方法とその実装手順をまとめます。 今回はSpotlightでの解錠について説明します。

Spotlight(検索)とは

Spotlightの使い方

Spotlight(検索)について普段から活用している人も多いと思いますが、ロック画面やホーム画面を右スワイプ、あるいは、ホーム画面を下にスワイプした時に出てくる「検索窓」のことです。

  • 右にスワイプ時に表示される検索窓(をTap)

  • 下にスワイプ時に表示される検索窓

Spotlightでの解錠方法

  • Spotlight検索画面でキーワード(アプリで設定された「解錠」文字列、ドア名など)を入力すれば、アプリ経由で解錠できる鍵/ドア一覧が表示される。
    • 鍵/ドア名を「Home」のようは一般的なキーワードにした場合、関係ない検索結果もたくさん表示されてしまうので、アプリ名か独自キーワードで工夫する必要がある。
    • 頻繁に使われている鍵/ドアの場合、自動的に最上位に表示されるので(iOSの独自アルゴリズムなので、表示順はアプリで制御できない)、使えば使えるほど便利になる。

  • 一覧に表示されている鍵/ドアをTapすれば、アプリが表示され、解錠まで1Tapで実現可能。

CoreSpotlightの実装

1Tapで解錠するには、iOSのCoreSpotlightのFrameworkを使って実装する必要があります。

Spotlightへの登録

検索する情報をItemとしてSpotlightのIndexに登録する必要があります。 Item数は数千個までなら最適に機能できるようです。

Core Spotlight works best when you have no more than a few thousand items.

  • CoreSpotlightを使うので、importする
import CoreSpotlight
  • 検索項目ごとにCSSearchableItemAttributeSetのインスタンを生成し、それそれのインスタンスのpropertiesを設定する。例えば、複数の鍵/ドアを持っている場合、それぞれの鍵/ドアのCSSearchableItemAttributeSetのインスタンを生成。
    • iOS13までinit(itemContentType: String)、iOS14からはinit(contentType: UTType)を使ってインスタンスを生成
    • シンプルに検索結果にtitle、画像を表示する場合、.imageのcontentTypeを指定する
    • 基本propertiesとして、title、contentDescription、keywords、thumbnailDataを設定する

  • keywordsには検索しやすい、引っかかりやすいキーワードを複数指定する。例えば、アプリ名、「解錠」などアプリ専用単語、鍵/ドア名など。
let attributeSet = CSSearchableItemAttributeSet(contentType: .image)
attributeSet.title = "解錠 \(key.name)"
attributeSet.contentDescription = "\(key.name)を解錠します"
attributeSet.keywords = ["XXXKey", "解錠", key.name]
attributeSet.thumbnailData = thumbnail
  • 鍵/ドアごとの一意ID(uniqueIdentifier)を指定したCSSearchableItemのインスタンスを生成し、上記のCSSearchableItemAttributeSetのインスタンスを関連付ける。
    • サーバーから取得した鍵/ドアのidが一意になっているので、そのまま指定すればOK
    • uniqueIdentifierに指定されたIDはCSSearchableItemActivityIdentifierをキーとしてNSUserActivityのuserInfo登録されるので、検索結果から鍵/ドアの識別に使われる。
    • groupで複数のItemを管理する場合、domainIdentifierも指定する。com.myCompany.myContentType(domainの逆順)の形式の任意の一意文字列を指定すればOK。
// ble/遠隔を区別する場合、prefixなどをつける
let id = "ble_" + key.id
let domainId = "xxx.yyy.CoreSpotlight.key"
let item = CSSearchableItem(uniqueIdentifier: id, domainIdentifier: domainId, attributeSet: attributeSet)
  • IndexにItemを登録する
    • 複数の鍵/ドアがある場合、配列で一括登録
CSSearchableIndex.default().indexSearchableItems([item]) { (error) in
}

Item登録/削除タイミング

アプリにログインし、サーバーから鍵/ドア一覧情報を取得する度にItemを登録し(2回目以降の場合、既存Itemを一度削除してから再登録)、ログアウトした時全てのItemを削除します。

  • 鍵/ドア情報が変わる可能性があるので、一覧を取得する度に再登録する(一度全て削除してから登録)
    • 削除した場合、今までの評価とランキング情報がなくなるかも?(詳細不明)
    • 更新方法はないようなので、indexSearchableItemsに同じIDを渡して再登録
  • CSSearchableIndexDelegateを実装しても良いかも
  • Batch処理も可能

https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/AppContent.html#//apple_ref/doc/uid/TP40016308-CH7-SW1

Spotlight検索結果からItemをTapした時の解錠処理(UIKit AppとSwiftUI App)

Tapした時のイベント処理はUIKit AppとSwiftUI Appでの処理が異なるので、それぞれの処理について説明します。

UIKit Appの場合

AppDelegateの以下のメソッドでイベントを拾って処理を行います。

  • Itemに指定されたuniqueIdentifierがNSUserActivityのuserInfoのCSSearchableItemActivityIdentifier(定義値:kCSSearchableItemActivityIdentifier文字列)キーの値として渡されるので、そこから鍵/ドアをIDを取得し、解錠処理を行う。
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard let userInfo = userActivity.userInfo else {
        return false
    }
    guard userActivity.activityType == CSSearchableItemActionType else {
        return false
    }
    guard let itemId = userInfo[CSSearchableItemActivityIdentifier] as? String else {
        return false
    }

    // itemIdから鍵/ドアのIDを取得し、解錠処理を行う

    return true
}

SwiftUI Appの場合

ViewのonContinueUserActivityメソッドでイベントを拾って処理を行います。

  • 基本的に解錠処理を表示する画面でイベントを拾えばOK。例えばTabViewの場合は下記になる。
struct ContentView: View {
    var body: some View {
        TabView(selection: $selection) {
            ...
        }
        .onContinueUserActivity(CSSearchableItemActionType, perform: handleSpotlight(_:))
    }
    
    func handleSpotlight(_ userActivity: NSUserActivity) {
        guard let userInfo = userActivity.userInfo else {
            return
        }
        guard let itemId = userInfo[CSSearchableItemActivityIdentifier] as? String else {
            return
        }
        // itemIdから鍵/ドアのIDを取得し、解錠処理を行う
    }
}

解錠処理の実装

Akerun公開APIで合鍵発行、解錠が可能です。 具体的な実装は割愛します。弊社のAkerun Developersと下記の関連記事をご参照ください。

developers.akerun.com

akerun.hateblo.jp

akerun.hateblo.jp

まとめ

実装も簡単な便利な機能なので、積極的にSpotlightを使いましょう。

次回は、HomeScreenQuickActionについて書こうと思います。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com