便利な解錠方法-NSUserActivity-SiriShortcuts解錠
WebエンジニアのBunです。主にiOSアプリの開発を担当しています。
日々いろんな解錠方法について考えています。特にiOSアプリで使えるいろんな便利な機能を使った解錠方法とその実装手順をまとめます。 今回はNSUserActivityを利用したSiri Shortcutsでの解錠について説明します。
NSUserActivityとは
抽象的な言い方だと、NSUserActivityは「ある時点でのアプリの状態」を表現します。
ユーザーがアプリを使って、コンテンツの表示、ドキュメントの編集、Webページの表示、ビデオの視聴などを行った時の状態(関連情報)をNSUserActivityのオブジェクトとして保存すれば、再度アプリが起動された時、上記の状態から復元することができます。
一例として、以下が可能です。詳細について、今後順次紹介します。
- Shortcutsアプリに登録したShortcutのActionを実行した時、指定画面に遷移させたり、機能を実現可能
- 音声でアプリを起動し、指定画面に遷移させたり、機能を実現可能
- Hand-off/Hand-freeでMacで途中までの作業をそのままシームレスでiPhoneで作業再開可能
- 端末内での検索可能
- Webと連携すれば、Global検索も可能
- iOS15からのQuickNoteでも使われている(まだ使ったことない)
Siri Shortcutsの使い方(解錠方法)
- Siri Shortcutsに登録すれば、使用頻度が上がる時、Siriからの提案にShortcut(解錠)が表示され、1Tapで解錠可能
※Siriからの提案:Home画面を下にSwipe、ロック画面で左にSwipeした時表示される検索窓(Spotlight)の一番上位に表示される
- Siriを呼び出して、音声から解錠可能。登録済みの音声フレーズ「あけるん」を言えば、アプリが起動され、指定画面に遷移し、対象ドアを解錠できる。
※端末によるかも知れないが、 Home画面(生体認証後のLock画面)でサイドボタンを2回ClickでSiriを呼び出せる。もちろん「Hey Siri」でもOK
- オートメーションを作れば、「xxx」の時「解錠」することも可能。最後に説明する。
Siri Shortcutsの実装方法
Siri Shortcutsの登録
Siri Shortcutsの登録方法には、NSUserActivityを利用した方法とIntentsを利用する方法がありますが、今回は前者について説明します。Intentsについてはまた別の機会でご紹介します。
NSUserActivityTypesを定義
Info.plistのNSUserActivityTypesにNSUserActivity作成に必要なIDを定義します。 一意であれば何でも良いですが、アプリのbundleId+機能ごとの文字列で良いと思います。
<key>NSUserActivityTypes</key> <array> <string>$(PRODUCT_BUNDLE_IDENTIFIER).unlock-door</string> </array>
アクションを行うNSUserActivityオブジェクトを生成
- オブジェクト生成
class SKUserActivity { enum ActivityType: String { case doorUnlock = "unlock-door" case keyShare = "share-key" var id: String { // TODO: add team id (info.plist) return (Bundle.main.bundleIdentifier ?? "") + "." + self.rawValue } } class func create(_ type: ActivityType, _ title: String, _ phrase: String? = nil) -> NSUserActivity { // Info.plistに登録したID let userActivity = NSUserActivity(activityType: type.id) userActivity.persistentIdentifier = type.id userActivity.title = title // 音声フレーズ(コマンド)。Siri Shortcutsに登録時変更可能 userActivity.suggestedInvocationPhrase = phrase // Siriからの提案を可能にする userActivity.isEligibleForPrediction = true // 検索可能にする userActivity.isEligibleForSearch = true return userActivity } }
- Spotlight検索結果に表示する場合
let attributeSet = CSSearchableItemAttributeSet(contentType: .image) // 実際userActivity.titleが表示され、このtitleは表示されない attributeSet.title = "Test" attributeSet.contentDescription = "Unlock" userActivity.contentAttributeSet = attributeSet
アクションで開く画面のUIViewControllerにオブジェクトを設定
NSUserActivityのオブジェクトを画面(UIViewController)に紐づけることで、Shortcutsアプリに表示され、登録可能になります。(一度アプリを起動し、該当画面に遷移する必要があるようです)
override func viewDidLoad() { super.viewDidLoad() self.userActivity = self.siriUserActivity }
Siri Shortcutsボタンを追加
ユーザーが簡単にSiri Shortcutsを登録できるように、画面に専用ボタンを配置することも可能です。
UIKitの場合、上記のUIViewControllerにINUIAddVoiceShortcutButtonを配置すれば良いですが、SwiftUIの場合、INUIAddVoiceShortcutButtonそのまま使えないので、UIViewControllerRepresentableを使って、UIKit->SwiftUIパーツに変換する必要があります。
- Siriボタンのパーツを作成
import Foundation import SwiftUI import Intents struct SiriButton: UIViewControllerRepresentable { private let userActivity: NSUserActivity init(userActivity: NSUserActivity) { self.userActivity = userActivity } func makeUIViewController(context: Context) -> some UIViewController { return SiriShortcutViewController(userActivity: self.userActivity) } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } }
- SiriShortcutViewControllerを作成し、INUIAddVoiceShortcutButtonを配置する
import Foundation import SwiftUI import IntentsUI class SiriShortcutViewController: UIViewController { private let siriUserActivity: NSUserActivity init(userActivity: NSUserActivity) { self.siriUserActivity = userActivity super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() self.userActivity = self.siriUserActivity // これはなくても良い?(詳細不明) self.userActivity?.becomeCurrent() let siriButton = INUIAddVoiceShortcutButton(style: .blackOutline) siriButton.shortcut = INShortcut(userActivity: self.siriUserActivity) siriButton.delegate = self siriButton.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(siriButton) self.view.centerXAnchor.constraint(equalTo: siriButton.centerXAnchor).isActive = true self.view.centerYAnchor.constraint(equalTo: siriButton.centerYAnchor).isActive = true } }
- SwiftUIの画面にSiriボタンを配置
struct TestView: View { var body: some View { VStack() { SiriButton(userActivity: SKUserActivity.create(.doorUnlock, "unlock", "あけるん")) } } }
SiriボタンでSiri Shortcutsに追加する時の処理
Siri Shortcutsに新規追加、編集、削除時の画面表示するための最小限の実装になります。
extension SiriShortcutViewController: INUIAddVoiceShortcutButtonDelegate { func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) { addVoiceShortcutViewController.delegate = self // addVoiceShortcutViewController.modalPresentationStyle = .formSheet // Shortcut画面を閉じる時の動作検知 addVoiceShortcutViewController.presentationController?.delegate = self self.present(addVoiceShortcutViewController, animated: true, completion: nil) } func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) { editVoiceShortcutViewController.delegate = self // Shortcut画面を閉じる時の動作検知 editVoiceShortcutViewController.presentationController?.delegate = self self.present(editVoiceShortcutViewController, animated: true, completion: nil) } } extension SiriShortcutViewController: INUIAddVoiceShortcutViewControllerDelegate { func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) { controller.dismiss(animated: true, completion: nil) } func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) { controller.dismiss(animated: true, completion: nil) } } extension SiriShortcutViewController: INUIEditVoiceShortcutViewControllerDelegate { func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) { controller.dismiss(animated: true, completion: nil) } func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) { controller.dismiss(animated: true, completion: nil) } func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) { controller.dismiss(animated: true, completion: nil) } } extension SiriShortcutViewController: UIAdaptivePresentationControllerDelegate { func presentationControllerWillDismiss(_ presentationController: UIPresentationController) { // Shortcut will be closed } func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { // Shortcut was closed } }
Siriに追加したら、Shortcutsアプリにも表示されます。
直接Shortcutsアプリから登録する場合、ある程度アプリを使わないと表示されないようです。
下記のAppをTapし、表示されるアプリ一覧には表示されません。
Siri Shortcutsから解錠
Shortcut、Siri音声コマンド、Spotlight検索窓のSiriからの提案に表示されているShortcutなどから1Tapでアプリを起動し、解錠することが可能です。 下記でアクションをハンドリングします。
- UIKitアプリの場合、以下のDelegate関数を実装
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { guard let userInfo = userActivity.userInfo else { return false } guard userActivity.activityType == SKUserActivity.ActivityType.doorUnlock.id else { return false } // userInfoから鍵/ドアのIDを取得し、関連画面に遷移し、解錠処理を行う return true }
- SwiftUIアプリの場合、表示する画面で以下を実装
struct ContentView: View { var body: some View { NavigationView { ... } .onContinueUserActivity(SKUserActivity.ActivityType.doorUnlock.id, perform: handleShortcut(_:)) } } extension ContentView { func handleShortcut(_ userActivity: NSUserActivity) { if let userInfo = userActivity.userInfo { // userInfoから鍵/ドアのIDを取得し、解錠処理を行う print("userInfo: \(userInfo)") } }
解錠処理の実装
Akerun公開APIで合鍵発行、解錠が可能です。 具体的な実装は割愛します。弊社のAkerun Developersと下記の関連記事をご参照ください。
Siri Shortcutsを使った便利機能(拡張版解錠方法)
Shortcutsアプリからオートメーションを作成すれば、色んな方法で解錠することができます。
iPhone背面タップして解錠
iOS設定->アクセシビリティ->タッチ->背面タップからアプリのShortcutを登録します。
QRコードで解錠
ShortcutsアプリからQRスキャンしてアプリを起動するShortcutを作成し、Home画面に追加します。
- ギャラリーからマイショートカットにQRコードスキャンを追加
- 既存アクションを削除し、自作アプリを登録
NFCタグで解錠
新規オートメーションを作成し、NFCスキャンをトリガーに、アプリをアクションに登録します。
まとめ
NSUserActivityではいろんなことができます。Webと連携することも可能です(別の機会で書く予定)。
その一つのSiri Shortcutsを実装すれば、iOS純正アプリShortcutsを使って無限な便利な機能を提供できそうです。
次回は、Widget、AppClipについて書こうと思います。
株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co
Akerun Proの購入はこちらから akerun.com