Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

対話を頑張らなくても作れるSiri Shortcuts向けIntents App Extension スライド書き起こしと補足

こんにちは、モバイルクライアントグループの明渡です。

iOSDC Japan 2021に採択していただいた、「対話を頑張らなくても作れるSiri Shortcuts向けIntents App Extension」のトーク収録を終えました!

数えてみると、合計6回ほど収録し直していました。 1週間近く練習と推敲を重ねていたのですが、撮ったものを見直すと気になる点が続々出てきて、ついつい何度も撮り直してしまいました。

当日他のセッションを視聴した方も、Siri Shortcutsの実装に少しでも興味があれば、是非動画の方も後日観てみていただけると嬉しいです。

fortee.jp

こちらは、上記トークをテーマとした以下の内容を含む記事となっております。

  • スライドの内容書き起こし
  • スライド作成過程で削った発表内容

前提

トークで話すこと

アプリが起動していなくても動作するよう、メインターゲットとは異なるIntents App Extensionを用いて処理が完結するSiri Shortcuts実装の一部

  • 実装するうえで押さえておきたいポイントを、リリース済みのStudyplusアプリへ実際に含まれるソースコード等も例として記載しつつ解説
  • 全体的な実装について知りたい方は、Appleのサンプルプロジェクト、SoupChefがおすすめ(実際に参考にしつつ実装を進めました)

トークで話さないこと

Intents App Extensionを用いるが、アプリが起動済みであることを前提として動作する実装について

補足: 具体例

WWDC動画の中では、料理のレシピを表示するアプリ内にて端末を手で操作せずにSiriへ呼びかけて次の工程を表示するのに使われていました。

メインのターゲットが起動済みの前提なので、アプリが起動していなくても動作させる実装より、事前準備の難易度が低くとっつきやすいはずです。

Studyplusで今回の実装をするにあたり、着手当初マルチモジュール化があまり進んでおりませんでした。 メインターゲット外から利用したい処理を利用するためのリファクタリングに取り組んだところ、工数にして丸2ヶ月強かかりました。

そのリファクタリングを進めたおかげでWidgetを先にリリースできた副次的効果もあったのですが、Siri Shortcutsの実装自体よりずっとしんどかったです。

NSUserActivityを用いた、アプリ内の特定の画面を開くのみなどのごく単純なショートカット対応の実装について

補足: 2021年(iOS 14〜15)時点での実装

NSUserActivity、およびSiri Shortcutsに近年目立ったアップデートはありません。

よって、「iOS 13における」と記載があるものの、現在もこちらの資料にまとめた実装方法が現役です。

トークで伝えたいこと

  • 対話の設計をきちんとしなくても、実用に耐えうるSiri Shortcutsは作れる
    • ただし、ユーザーにエラーと知らせるケースは徹底的に網羅したほうが親切
  • Siri Shortcutsの実装自体は難しくない!
    • Xcode上でしか開けない専用ファイル(.intentdenifition)を用いる必要があるが、開発者が気を遣って書くソースコードの量は少なめ
    • WWDCの動画を観ながら実装が面倒だった、エラーケース実装などを発表資料に詰め込んだので、皆様のSiri Shortcuts実装時に役に立てば嬉しい

Studyplusでリリースした勉強記録を入力するSiri Shortcuts概要

Studyplusとは

学習内容を記録して見える化しつつ、記録をシェアして切磋琢磨し合うSNSも兼ねたアプリです。 利用ユーザーは学生の比率が高いのですが、年齢を問わず「何らかの勉強をしたい!」という方々に愛用していただいています。

例えば、ちょっとした読書なども記録をつけることで読み続けるモチベーションが上がったりするものです。 積ん読に心当たりがあるなど、気になる方は是非インストールしてみてください。

f:id:m_yamada1992:20210831102139p:plain

Siri Shortcutsのリリース

2021年5月10日、Studyplusのコア機能である、勉強記録の入力をSiri Shortcuts経由で可能にする機能をリリースしました。

勉強記録とは

  • 勉強した日時と、以下の勉強に関する値を中心に構成された記録のこと
    • ★ 勉強に利用した教材
    • ★ 勉強時間
    • ★ 勉強量(合計or範囲)
    • ページ毎・章毎など単位は教材の設定に準拠
    • メモ
    • 添付画像

勉強記録を保存すると...

タイムラインに投稿されて他のユーザーからいいねやコメントを受け付けることができたり、レポートとして集計され、あとから見返すことができます。

Siri Shortcutsへ対応した操作

勉強記録を入力する画面にて可能な操作に一通り対応しました。具体的には、以下の通りです。

  • 勉強したことを記録する
  • ストップウォッチ・タイマーの計測開始をする
    • 勉強している教材をストップウォッチ・タイマーに関連付けることも可能
  • ストップウォッチ・タイマーの計測一時停止・再開する
  • ストップウォッチ・タイマーで計測中の時間を確認する
  • ストップウォッチ・タイマーでの計測を完了し、勉強を記録する

対応した操作の利用状況

※2021年5月10日〜8月27日時点の集計結果です。

  • 勉強記録関連の操作をショートカットで実行した
    • 回数: 111,650
    • 人数: 11,564
補足: 集計方法

2021年5月までの実装当時、導入済みのFirebase Analyticsではメインターゲット未起動だとイベントが送信できませんでした。 そのため、ショートカット実行時にログをローカルDBへ保存し、次回アプリ起動時に一括送信して集計しています。

この実装にした要因の1つは、Firebase Analyticsを別ターゲットから利用しようとした際、何故かFirebase Dynamic Linksでビルドエラーが発生する事象でした。 ところが、先日8月3日にリリースされたFirebaseのバージョン8.5.0にて、Dynamic Linksが拡張ターゲットから呼び出された際のビルドエラーを改善したそうです。

これから実装する場合は、メインターゲットとは別のターゲットから素直にイベント送信する実装を試してみるとすんなり可能かもしれません。

Siri Shortcutsで操作を受け付けるアクションの選定

勉強を記録するという1つの機能への操作について、アクションをどのような単位で受け付けるようにしたかお話します。

なお、前提としてSiri Shortcutsを実装する対象の機能は定まっている状態で、どのようにアクションを切り分けたかという方が近いです。

補足:アクションの”選定”について

Siri Shortcutsを実装するのに向き・不向きな機能はどのようなものか選り分ける方法が知りたいという方は、 Designing Great Shortcutsを参考にしてください。

AppleがSiri Shortcutsの実装を勧めている「アプリを利用する上で何回も繰り返し行われる操作」について、実装対象の選定手順を具体例つきで解説しています。

Studyplusで対応した操作を振り返る

対応した操作を厳密に分けると、以下の通りです。

  • 勉強したことを記録する
  • これから勉強する教材でストップウォッチの計測を開始する
  • これから勉強する教材でタイマーの計測を開始する
  • ストップウォッチの計測を一時停止する
  • タイマーの計測を一時停止する
  • ストップウォッチの計測を再開する
  • タイマーの計測を再開する
  • ストップウォッチで計測中の時間を確認する
  • タイマーで計測中の時間を確認する
  • ストップウォッチでの計測を完了し、勉強を記録する
  • タイマーでの計測を完了し、勉強を記録する

「勉強を記録する」という1つの機能に対して、できることが多いです。

もしも、「1機能だから1つのアクションで対応しよう!」と実装してしまうと...

以下のようなやりとりが想定できます。

  • 勉強したことを記録したいユーザーが、勉強を記録するショートカットを実行
    • Siriからの回答は「どのように記録しますか?」で、次に進むには選択肢を選ぶ必要がある
      • 勉強したことを記録
      • ストップウォッチで計測を始める
      • タイマーで計測を始める
      • 計測中のストップウォッチを一時停止
      • etc...
  • ストップウォッチで計測しながら勉強したいユーザーが、勉強を記録するショートカットを実行
    • Siriからの回答は「どのように記録しますか?」で、次に進むには選択肢を選ぶ必要がある
      • 勉強したことを記録
      • ストップウォッチで計測を始める
      • タイマーで計測を始める
      • 計測中のストップウォッチを一時停止
      • etc...

ユーザーはショートカットを実行する前にやりたいことが決まっているのに、毎回選ばなくてはいけない。 実行するたびに毎回このような問いかけがされると、うんざりして使わなくなる人もいるかもしれません。

もしも、すべての操作をそれぞれ1個ずつのアクションとして実装してしまうと...

以下の操作をすべて、個別にアクションを作成したとしましょう。

  • 勉強したことを記録する
  • これから勉強する教材でストップウォッチの計測を開始する
  • これから勉強する教材でタイマーの計測を開始する
  • ストップウォッチの計測を一時停止する
  • タイマーの計測を一時停止する
  • ストップウォッチの計測を再開する
  • タイマーの計測を再開する
  • ストップウォッチで計測中の時間を確認する
  • タイマーで計測中の時間を確認する
  • ストップウォッチでの計測を完了し、勉強を記録する
  • タイマーでの計測を完了し、勉強を記録する

設定できるアクションの個数に圧倒されて、そもそも利用を諦めるユーザーが多くなるかもしれません。

対応する操作をまとめる

1機能を1アクションにまとめるとまでいかずとも、アクションの数は必要最低限にとどめたいです。

そこで、 アクションを利用する場面をまとめる基準とし、「呼びかけるフレーズが類似しそうな操作」を1つのアクションとしました。

勉強記録のアクションを利用場面毎にまとめた結果

以下のように、7個のアクションとなりました。

  • 勉強を記録する
    • 勉強したことを記録する
  • ストップウォッチを開始する
    • これから勉強する教材でストップウォッチの計測を開始する
  • タイマーを開始する
    • これから勉強する教材でタイマーの計測を開始する
  • 勉強を休憩する
    • ストップウォッチの計測を一時停止する
    • タイマーの計測を一時停止する
  • 勉強を再開する
    • ストップウォッチの計測を再開する
    • タイマーの計測を再開する
  • 記録中の勉強を見る
    • ストップウォッチで計測中の時間を確認する
    • タイマーで計測中の時間を確認する
  • 勉強を終わる
    • ストップウォッチでの計測を完了し、勉強を記録する
    • タイマーでの計測を完了し、勉強を記録する

多めに感じる個数となってしまいましたが、どのアクションを実行すればどのような結果が得られるか、直感的に分かりやすく見えます。

ストップウォッチとタイマーの開始だけ、利用場面でまとめず、入力を受け付けるパラメータの都合で分けました。 パラメータ設計についても後ほどお話します。

利用場面毎にアクションを分離した結果

  • 蓄積したAnalyticsイベントから察するに、利用するユーザーにとっては”あって当たり前”の機能として定着している様子
    • ユーザーから「Siri Shortcutsはどうやって利用するか」の問い合わせは少々くるものの、「計測開始はできたが一時停止の方法が分からない」等の問い合わせはきていない
  • アクションを実行する方法さえ分かれば、利用方法は困らない

対話の設計

アクション毎に入力を受け付けるパラメータ設計

Appleのおすすめ対話設計

  1. Siriとユーザー間で想定するやりとりを台本形式に起こす
  2. 台本をもとにフローチャートを起こす

この設計手順は音声対話UIというくくりで基本的に同じなので、しっかり設計をするとAlexaスキルやGoogle アシスタント向けアプリなど他の音声対話UIにも流用が可能です。

Studyplusにて勉強記録機能の対話設計

勉強記録機能の7個のアクションのうち、★の項目が勉強記録を投稿するアクションで、取り扱うパラメータが多いものになります。

  • ★ 入力された内容で勉強を記録する
  • 勉強記録のストップウォッチを開始
  • 勉強記録のタイマーを開始
  • 勉強記録のストップウォッチ・タイマーを一時停止
  • 勉強記録のストップウォッチ・タイマーを再開
  • ★ 勉強記録のストップウォッチ・タイマーを完了
  • 計測中の勉強記録を確認
勉強記録で入力を受け付けているパラメータ
  • 勉強開始時刻
  • 教材
  • 勉強時間
  • 学習量
    • 合計or範囲
  • メモ
  • 画像
  • Twitterの共有ステータス

アクションを実行してから実際に投稿を終えるまでにSiriとユーザー間でやりとりするには、項目が多すぎです。 このため、可能な範囲でアプリの画面から入力を受け付けるより項目を削減しました。

  • 勉強開始時刻
    • アクション実行時の日時を自動的に入力
  • 画像
    • 端末の画面を見る操作が必須で本末転倒、投稿後に後付け可能なのでそちらで対応
  • Twitterの共有ステータス
    • 前回のON/OFFステータスを引き継ぎ
ショートカット経由で入力を受け付けるパラメータ
  • 教材
  • 勉強時間
  • 学習量
    • 合計or範囲
  • メモ

ユーザーとの対話が成り立つように設計するには、問題がありました。

上記のうち、教材・勉強時間・学習量が条件付きで必須になったり、入力不可になるパラメータだったのです。

アクションに設定するサマリについて

今年発表されたWWDCの動画、Design great actions for Shortcuts, Siri, and Suggestionsでも言及がありました。

f:id:m_yamada1992:20210831172803p:plain

ショートカットで入力を受け付けるパラメータは、以下のような前提があります。

  • 必須パラメータ
    • Summaryに含める
  • 任意のパラメータ
    • 任意で設定可能な、Summary欄外のオプションに含める

結論からいいますと、Studyplusの勉強記録機能では左側のBadパターンで実装しております。

Summaryの定義方法

SummaryはCustom Intentsを定義する、拡張子.intentdenifitionファイルのうち、Supported Combinations欄で定義します。

f:id:m_yamada1992:20210831174456p:plain

1個のアクションに対して複数パターン、定義は可能です。

しかしながら、実際にユーザーがアクションを設定する際に表示されるSummaryは、一番上のもの固定です。 ユーザーが任意に、どのSummaryを使って入力するか選べるものではありません。

Appleおすすめの対話設計に則ってアクションの実装をした場合

勉強記録の投稿を伴うアクションは以下の通り、3つに分けるべきです。

  1. 勉強時間を記録
    • 教材(任意)
    • 勉強時間(必須)
    • 勉強量
  2. 勉強量を記録
    • 教材(必須)
    • 勉強時間(任意)
    • 勉強量(必須)
  3. 勉強時間と勉強量を記録
    • 教材(必須)
    • 勉強時間(必須)
    • 勉強量(必須)

3番目の全部必須のアクションは2番でカバー可能ですが、勉強時間・勉強量共に記録できるとひと目で分かるアクションも準備したほうが親切です。

Appleおすすめの対話設計に則った場合、できあがるアクション一覧

以下の通り、★のアクションがさらに分かれます。

  • ★ 入力された内容で勉強を記録する
    • 勉強時間を記録
    • 勉強量を記録
    • 勉強時間と勉強量を記録
  • 勉強記録のストップウォッチを開始
  • 勉強記録のタイマーを開始
  • 勉強記録のストップウォッチ・タイマーを一時停止
  • 勉強記録のストップウォッチ・タイマーを再開
  • ★ 勉強記録のストップウォッチ・タイマーを完了
    • 勉強時間を記録
    • 勉強時間と勉強量を記録
  • 計測中の勉強記録を確認

ストップウォッチ・タイマーを完了するアクションは、計測時間が必ず含まれているので2つです。

ただでさえ多いのに、合計10個のアクションができあがります。

Appleおすすめの対話設計での実装を検討した結果

似たようなアクションが多すぎてユーザーの混乱を招くことが想像できてしまったので、Appleおすすめの設計通りに実装するのは断念しました。

このトークのタイトルである"対話を頑張らなくても"というのは、この対話設計をきちんとしていないことについて指しています。

Appleおすすめ設計を諦めた結果

  • アクションは利用する場面ごとに切り分けて必要最小限に留める
  • 勉強記録の投稿を伴うアクションはパラメータ毎に切り分けず、包括的にサポート
    • 条件付き必須パラメータ・条件付き入力不可パラメータは、ショートカット実行時に判定してエラーとして弾く
  • すべてのパラメータを音声対話でも適切に入力を受け付けられる想定を諦めて、ショートカットアプリの手入力UI経由での実行時には想定通り動作することを目指した

入力されたパラメータのエラー処理設計+実装例

エラーとするケースを整理

パラメータ毎のバリデーションその1

最初に、パラメータ毎にエラーとする条件を書き起こしました。 それぞれのパラメータで、機能の仕様的な上限・下限の範囲外のため弾くケースを書き起こします。

  • 勉強時間
    • 1440分(24時間)より大きい値
  • 学習量
    • 合計or範囲
      • 上限(9999)より大きい値
      • 下限(0)より小さい値
パラメータ毎のバリデーションその2

次に、別のパラメータの入力がどんな値であるかを条件に、エラーとするケースについて書き起こします。

f:id:m_yamada1992:20210831181408p:plain

パラメータの入力は、スライドの通り定義されている順番に解決されます。

  • 例: ②の勉強時間を解決する場合
    • ①の教材が入力済みなので、resolveメソッド内で入力された教材の内容を確認しながらエラーを返却可能

Studyplusでは以下の通り、各パラメータを解決する際に、入力済みの値との組み合わせが勉強記録として成り立たないケースを書き起こしました。

  • 勉強時間
    • 教材なしかつ勉強時間なし
  • 学習量
    • 合計or範囲
      • 範囲の場合、開始が終了より大きい値
      • 範囲の場合、開始または終了のみ1以上の値
      • 教材なしで合計or範囲へ1以上の値を設定
    • 勉強時間なしかつ学習量なし
パラメータの入力値では判定できないエラー

最後に、パラメータ毎のバリデーションでは判定できない、入力を受け付けた値以外の別の要素が原因で発生するエラーを書き起こします。

Studyplusでは主に、現在のユーザー状態によってアクションを完了できないケースを書き起こしました。

  • インターネットの接続ステータス
    • オフラインである
    • オンラインの状態でアプリを起動時に改めて送信処理を試行する仕様
  • Studyplusアプリでのログインステータス
    • 未ログインで、勉強記録を投稿する先のアカウントが不明
  • 教材
    • 選択した教材が現在本棚に存在しない
      • 自分の本棚に存在する教材でないと勉強記録を入力できない仕様
  • メモ
    • NGワードを含む
      • NGワードはカスタマーサポートで常時調整できるため、有無をサーバーで判定する仕様

整理したエラーを実装

バリデーションエラーを定義

まずはパラメータのバリデーションで弾くことが可能なケースを定義します。 場所は、.intentdenifitionファイルのうち、該当パラメータのValidation Errorsという欄です。 こちらへ、パラメータ毎のバリデーションその1・その2で書き起こしたケースをすべて定義します。

f:id:m_yamada1992:20210831182614p:plain

なお、スライドのValidation Errors欄のうち上3個は、TypeがIntegerのパラメータ作成時に自動生成されるケースです。

パラメータのType毎に自動生成されるケースについては、条件により自動でエラーを表示してくれるので、resolveメソッドでエラーを返す実装は不要です。 エラーの返却条件は、Validation Errors欄の上にあるInput欄へ指定した範囲以外の値が入力された場合になります。

バリデーションエラーを返却

Handlerクラスでパラメータ毎に入力された値をresolveメソッドで解決する際に、入力済みの値に応じて定義したエラーを返す実装をします。

public class PostStudyRecordIntentHandler: NSObject, PostStudyRecordIntentHandling {

    public func resolveEnd(
        for intent: PostStudyRecordIntent,
        with completion: @escaping (PostStudyRecordEndResolutionResult) -> Void) {
        guard let end = intent.end, Int(truncating: end) > 0 else {
            completion(
                PostStudyRecordEndResolutionResult
                    .notRequired()
            )
            return
        }
        guard intent.material != nil else {
            completion(
                PostStudyRecordEndResolutionResult
                    .unsupported(forReason: .noMaterial)
            )
            return
        }
        guard let start = intent.start, Int(truncating: start) > 0 else {
            completion(
                PostStudyRecordEndResolutionResult
                    .unsupported(forReason: .noStart)
            )
            return
        }
        /* lessThanStartValue, failureLackParameterのケースを省略 */
        completion(
            PostStudyRecordEndResolutionResult
                .success(with: Int(truncating: end))
        )
    }
}

バリデーションエラーとして定義したケースを返却する処理は、completionクロージャへPostStudyRecordEndResolutionResult.unsupported(forReason:)を渡している箇所です。 条件と一致した場合に、該当のエラーを返すよう実装していけば完了です。

補足: ~Result.unsupported以外の解決結果種別
  • needsValue()
    • 必須パラメータなのに値がなかった場合に利用。unsupportedと同様、エラーとして返却
  • success(with:)
    • 該当パラメータが想定通り入力された場合に利用。次のパラメータ解決処理へ進む
  • notRequired()
    • 任意パラメータなので、無視する場合に利用。successと同様、次のパラメータ解決処理へ進む
バリデーションで解決できないエラーを定義

次に、パラメータに入力された値のみでは判定できないエラーを定義します。 場所は、.intentdenifitionファイルのうち、該当アクションのCustom IntentsでのResponseにて、Response Templetesという欄です。 こちらへ、パラメータの入力値では判定できないエラーケースをis error responseのチェックをONにして定義します。

f:id:m_yamada1992:20210831185154p:plain

バリデーションで解決できないエラーを返却

IntentHandlerクラスのうち、パラメータ解決がすべて終わった後、アクションを実行するhandleメソッドで利用します。

public class PostStudyRecordIntentHandler: NSObject, PostStudyRecordIntentHandling {

    public func handle(
        intent: PostStudyRecordIntent,
        completion: @escaping (PostStudyRecordIntentResponse) -> Void) {
        guard CurrentUser.isLogin else {
            completion(
                PostStudyRecordIntentResponse(
                    code: .failureStatusLogout, userActivity: nil)
            )
            return
        }
        /* failureOffline, failureNotFoundBookshelfEntryのケース、その他の処理を省略 */
        StudyRecordRepository.create(studyRecord: studyRecord, success: { _ in
            completion(
                PostStudyRecordIntentResponse(
                    code: .success, userActivity: userActivity)
            )
        }, ngWordsDetected: {
            completion(
                PostStudyRecordIntentResponse(
                    code: .failureNgWordsDetected, userActivity: nil)
            )
        }
    }
}

定義したエラーを返却する処理は、completionクロージャへ渡すPostStudyRecordIntentResponsecode引数へ、.failure~を指定している箇所です。 パラメータ毎のresolveメソッドと同様、条件と一致した際に該当のエラーを返すよう実装していけば完了です。

補足: ~Responsecodeに渡す結果種別

resolveメソッドとは異なり、エラーを定義した欄であるResponse Templetesに含まれる以外のケースは指定できません。

successfailureはCustom Intents作成時に自動生成され削除不可なので、どのアクションのhandleメソッドでも共通で利用可能です。

押さえておきたい実装を紹介

アクション完了後、アプリに遷移してやりたいことがある場合の実装

勉強記録を投稿後に報告できる、今週の目標進捗について

今週の目標とは

Studyplusには、月曜日〜日曜日に勉強する時間や勉強量を、今週の目標として設定できる機能があります。 今週の目標を設定すると、記録済みの勉強記録の内容に応じて進捗率を表示したり、進捗をタイムラインへ共有できます。

f:id:m_yamada1992:20210831192220p:plain

今週の目標の進捗を報告する機会を、勉強記録を投稿完了直後にのみ設けています。 そのため、ショートカット経由で勉強記録を投稿したときも同様に、進捗報告ができる機会を設ける必要がありました。

また、条件を満たしても投稿は任意なので、投稿したいユーザーだけ投稿できるのが望ましいです。

Siri Shortcutsにおける今週の目標進捗投稿への対応

投稿は任意のため、今週の目標進捗を投稿する機能はSiri Shortcutsのアクション内で完結させませんでした。 投稿したい場合、下記の通りアクション実行完了を知らせるビューをタップして、アプリに遷移してから投稿してもらうことにしました。

f:id:m_yamada1992:20210831193320p:plain

実行完了を知らせるビュー内に「タイムラインに進捗を投稿する場合は、この記録をタップしてアプリを起動してください」と明記して、タップするとアプリへ遷移することも伝えています。

こちらの実行完了を知らせるビューでアプリへ遷移することを考慮する際には、注意点もあります。

f:id:m_yamada1992:20210831193844p:plain

アクション実行完了時に表示するビューは、以下の該当アクションに関する設定でユーザーが表示をOFFにすることができます。 OFFにするとこのビューは表示されないため、アクション完了後に必ずしたいことは、アクション内で完結してください。

アクション実行完了時のビューをタップでアプリ起動時の実装

アクションを実行するhandleメソッドの引数、completionクロージャに渡す~IntentResponseへ、userActivity引数を渡すだけです。

public class PostStudyRecordIntentHandler: NSObject, PostStudyRecordIntentHandling {

    public func handle(
        intent: PostStudyRecordIntent,
        completion: @escaping (PostStudyRecordIntentResponse) -> Void) {
        /* エラー処理や投稿処理は省略 */
        let userActivity = NSUserActivity(
            activityType: “Info.plist>NSUserActivityTypesに定義済みの名前")
        userActivity.title = "勉強を記録する"
        userActivity.userInfo = ["study_challenge": "今週の目標進捗の内容"]
        completion(
            PostStudyRecordIntentResponse(
                code: .success, userActivity: userActivity)
        )
    }
}

userActivity引数へは、NSUserActivityインスタンスへアプリ起動時に表示する画面の情報を詰めたうえで渡しましょう。

こちらのNSUserActivityインスタンスは、アプリ起動時に受け取ることができます。

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    func scene(
        _ scene: UIScene,
        continue userActivity: NSUserActivity) {

        // userActivity.activityTypeおよびuserInfoを解析して画面遷移
    }
}

SceneDelegateもしくはAppDelegateのうち、NSUserActivityを引数に受け取るメソッドで、渡したインスタンスを解析して該当画面を表示する実装をすれば完了です。

さいごに

発表でお話した内容は以上です!

発表した勉強記録機能のSiri Shortcuts実装にかかった工数は、おおよそ以下の通りです(作業は1人)。 なお、諸事情により半年以上にわたりこの機能の開発作業をしていたため、日数が少々曖昧です。 勉強記録と同程度の機能へSiri Shortcutsの実装をする際の目安程度に捉えてください。

  • 設計4日
    • 着手前の叩き作成に2日
    • 着手後に調査・検証して軌道修正に2日
  • 実装10日
    • パラメータやアクションの実行処理に6日
    • 実行完了時のビューのレイアウト作成・調整に4日

発表に含めたエラー処理まわりに関しては特に、作成した資料の情報が最初からまとまっていれば工数が短縮できたと考えています。

みなさまが開発しているアプリで、ユーザーが頻繁に利用している機能に心当たりがあれば是非、Siri Shortcutsへの対応を検討してみてください。

そしてSiri Shortcutsの実装をする際に、この発表の内容が少しでも役に立てば幸いです。