SiriKitの意図はあなたのアプリに適合しますか? もしそうなら、ここにそれらを使用する方法があります
公開: 2022-03-10iOS 5以降、Siriは、iPhoneユーザーがメッセージを送信したり、リマインダーを設定したり、Appleのアプリでレストランを検索したりするのを支援してきました。 iOS 10以降、一部の独自のアプリでもSiriを使用できるようになりました。
この機能を使用するには、アプリがAppleの事前定義されたSiriの「ドメインとインテント」に適合している必要があります。 この記事では、それらが何であるかを学び、アプリがそれらを使用できるかどうかを確認します。 ToDoリストマネージャーであるシンプルなアプリを取り上げ、Siriサポートを追加する方法を学びます。 また、SiriKitで導入された新しいタイプの拡張機能であるIntents拡張機能の構成とSwiftコードに関するAppleデベロッパWebサイトのガイドラインも確認します。
この記事のコーディング部分にたどり着くには、Xcode(少なくともバージョン9.x)が必要です。Siriを小さな作業に追加するので、SwiftでのiOS開発に精通しているとよいでしょう。アプリ。 Appleの開発者向けWebサイトで拡張機能を設定し、アプリにSiri拡張機能コードを追加する手順を実行します。
「HeySiri、なぜ私はあなたが必要なのですか?」
ソファに座ったまま、両手を使わずにスマートフォンを使用することもあります。画面に十分な注意を払うことができます。 たぶん、私は妹にテキストを送って、お母さんの誕生日を計画したり、Trelloで質問に答えたりします。 アプリが見えます。 画面をタップできます。 私は文字が打てます。
しかし、時計にテキストが届くと、ポッドキャストを聴きながら町を歩き回っている可能性があります。 電話がポケットに入っていて、歩きながら簡単に答えられません。
Siriを使えば、ヘッドフォンのコントロールボタンを押したままにして、「妹に2時までにそこにいることを伝えて」と言うことができます。 Siriは、外出先で携帯電話に完全に注意を向けることができない場合や、操作が少ない場合に最適ですが、数回のタップと大量の入力が必要です。
これらのやり取りにAppleアプリを使用したい場合は、これで問題ありません。 ただし、メッセージングなどの一部のカテゴリのアプリには、非常に人気のある代替手段があります。 乗車の予約やレストランでのテーブルの予約などの他のアクティビティは、Appleの組み込みアプリでは不可能ですが、Siriには最適です。
音声アシスタントに対するAppleのアプローチ
サードパーティのアプリでSiriを有効にするには、Appleはユーザーの声から音を取り出し、リクエストを満たすことができる方法でアプリに到達させるメカニズムを決定する必要がありました。 これを可能にするために、Appleはユーザーにリクエストでアプリの名前を記載するように要求しますが、残りのリクエストをどう処理するかについていくつかのオプションがありました。
- アプリにサウンドファイルを送信した可能性があります。
このアプローチの利点は、アプリがユーザーからのリクエストを文字通り処理できることです。 アマゾンやグーグルはすでに洗練された音声認識サービスを持っているので、このアプローチが好きだったかもしれません。 しかし、ほとんどのアプリはこれを非常に簡単に処理することはできません。 - それはスピーチをテキストに変えてそれを送ったかもしれません。
多くのアプリには洗練された自然言語の実装がないため、ユーザーは通常、非常に特定のフレーズに固執する必要があり、英語以外のサポートはアプリ開発者が実装する必要があります。 - 理解できるフレーズのリストを提供するように求められた可能性があります。
このメカニズムは、AmazonがAlexaで行うこと(「スキル」フレームワーク)に近く、SiriKitが現在処理できるよりもはるかに多くのAlexaの使用を可能にします。 Alexaスキルでは、Alexaが入力するプレースホルダー変数を使用してフレーズを提供します。 たとえば、「アレクサ、$TIME$
から$REMINDER$
を送信」— Alexaは、ユーザーの発言に対してこのフレーズを実行し、TIME
とREMINDER
の値を通知します。 以前のメカニズムと同様に、開発者はすべての翻訳を行う必要があり、ユーザーが少し違うことを言った場合、柔軟性はあまりありません。 - パラメータを使用してリクエストのリストを定義し、アプリに構造化されたリクエストを送信できます。
これは実際にはAppleが行っていることであり、その利点は、さまざまな言語をサポートできることであり、ユーザーがリクエストを表現するすべての方法を理解しようとするすべての作業を実行します。 大きな欠点は、Appleが定義したリクエストのハンドラーしか実装できないことです。 これは、たとえばメッセージングアプリを使用している場合は便利ですが、音楽ストリーミングサービスやポッドキャストプレーヤーを使用している場合は、現在SiriKitを使用する方法がありません。
同様に、アプリがユーザーに話しかける方法は3つあります。音声を使用する方法、変換されるテキストを使用する方法、言いたいことを表現してシステムに正確な表現方法を理解させる方法です。 最後の解決策(これはAppleが行うことです)はAppleに翻訳の負担をかけますが、それはあなた自身の言葉を使って物事を説明する限られた方法をあなたに与えます。
処理できるリクエストの種類は、SiriKitのドメインとインテントで定義されています。 インテントとは、連絡先にテキストメッセージを送信したり、写真を検索したりするなど、ユーザーが行う可能性のあるリクエストの一種です。 各インテントにはパラメータのリストがあります。たとえば、テキストメッセージには連絡先とメッセージが必要です。
ドメインは、関連するインテントの単なるグループです。 テキストの読み取りと送信はどちらもメッセージングドメインにあります。 乗車の予約と場所の取得は、乗車予約ドメインにあります。 VoIP通話の発信、ワークアウトの開始、写真の検索などのドメインがあります。 SiriKitのドキュメントには、ドメインとその目的の完全なリストが含まれています。
Siriに対する一般的な批判は、GoogleやAlexaと同様にリクエストを処理できないようであり、Appleの競合他社によって実現されたサードパーティの音声エコシステムがより豊富であるということです。
私はそれらの批判に同意します。 アプリが現在の意図に適合しない場合、SiriKitを使用することはできず、何もできません。 アプリが適合したとしても、Siriが言うまたは理解するすべての単語を制御することはできません。 そのため、アプリ内のことについて特定の話し方をしている場合、それをSiriに常に教えることができるとは限りません。
iOS開発者の希望は、Appleがその意図のリストを大幅に拡大することと、その自然言語処理がはるかに良くなることの両方です。 そうすれば、開発者が翻訳をしたり、同じことを言うすべての方法を理解したりしなくても機能する音声アシスタントができます。 また、構造化リクエストのサポートの実装は、実際にはかなり簡単です。自然言語パーサーを構築するよりもはるかに簡単です。
インテントフレームワークのもう1つの大きな利点は、Siriと音声リクエストに限定されないことです。 現在でも、マップアプリは、アプリのインテントベースのリクエスト(レストランの予約など)を生成できます。 これはプログラムで行われます(音声や自然言語からではありません)。 Appleがアプリにお互いの公開された意図を発見することを許可した場合、(xコールバックスタイルのURLとは対照的に)アプリが連携するためのはるかに優れた方法があります。
最後に、インテントはパラメーターを含む構造化されたリクエストであるため、アプリがパラメーターが欠落していること、またはいくつかのオプションを区別するための支援が必要であることを表現する簡単な方法があります。 その後、Siriはフォローアップの質問をして、アプリが会話を行うことなくパラメーターを解決できます。
ライドブッキングドメイン
ドメインと意図を理解するために、ライドブッキングドメインを見てみましょう。 これは、SiriにLyft車を入手するように依頼するために使用するドメインです。
Appleは、乗車をリクエストする方法とその情報を取得する方法を定義していますが、実際には、このリクエストを実際に処理できる組み込みのAppleアプリはありません。 これは、SiriKit対応アプリが必要な数少ないドメインの1つです。
インテントの1つは、音声を介して、またはマップから直接呼び出すことができます。 このドメインの目的のいくつかは次のとおりです。
- 乗車をリクエストする
これを使って乗車を予約します。 ピックアップとドロップオフの場所を指定する必要があります。また、アプリはパーティーのサイズと希望する乗り物の種類を知る必要がある場合もあります。 サンプルフレーズは、「<appname>で乗車を予約してください」などです。 - ライドのステータスを取得する
このインテントを使用して、リクエストが受信されたかどうかを確認し、場所を含む車両とドライバーに関する情報を取得します。 マップアプリは、このインテントを使用して、車が近づいてくるときに更新された画像を表示します。 - 乗車をキャンセルする
これを使用して、予約した乗車をキャンセルします。
この意図のいずれかについて、Siriはより多くの情報を知る必要があるかもしれません。 インテントハンドラーを実装するとわかるように、Intents拡張機能は、必要なパラメーターが欠落していることをSiriに通知でき、Siriはユーザーにそのパラメーターの入力を求めます。
マップによってプログラムでインテントを呼び出すことができるという事実は、インテントが将来どのようにアプリ間通信を可能にするかを示しています。
注:ドメインとその意図の完全なリストは、Appleの開発者向けWebサイトで入手できます。 ライドブッキングなど、多くのドメインとインテントが実装されたサンプルのAppleアプリもあります。
アプリへのリストとNotesドメインサポートの追加
さて、SiriKitの基本を理解したので、処理したいインテントごとに多くの構成とクラスを含むアプリでSiriのサポートを追加する方法を見てみましょう。
この記事の残りの部分は、アプリにSiriサポートを追加するための詳細な手順で構成されています。 あなたがする必要がある5つの高レベルの事柄があります:
- Appleの開発者向けWebサイトで、新しい資格を持つプロビジョニングプロファイルを作成して、アプリに新しい拡張機能を追加する準備をします。
- エンタイトルメントを使用するように(
plist
を介して)アプリを構成します。 - Xcodeのテンプレートを使用して、いくつかのサンプルコードを開始します。
- Siriの意図をサポートするコードを追加します。
-
plist
を介してSiriの語彙を構成します。
心配しないでください。これらのそれぞれについて説明し、拡張機能と資格について説明します。
Siriの部分だけに焦点を当てるために、私は簡単なToDoリストマネージャーであるList-o-Matを用意しました。
サンプルの完全なソースであるList-o-Matは、GitHubにあります。
それを作成するために私がしたのは、Xcode Master-Detailアプリテンプレートから始めて、両方の画面をUITableView
にすることだけでした。 リストとアイテムを追加および削除する方法と、完了したアイテムをチェックする方法を追加しました。 すべてのナビゲーションはテンプレートによって生成されます。
データを保存するために、 Codable
プロトコル(WWDC 2017で導入)を使用しました。このプロトコルは、構造体をJSONに変換し、 documents
フォルダーのテキストファイルに保存します。
私は意図的にコードを非常に単純に保ちました。 SwiftとViewControllerの作成の経験があれば、問題はないはずです。
これで、SiriKitサポートを追加する手順を実行できます。 高レベルの手順は、どのアプリでも、実装する予定のドメインとインテントでも同じです。 私たちは主にAppleの開発者向けWebサイトを扱い、 plist
を編集し、Swiftを少し作成します。
List-o-Matについては、リストとメモのドメインに焦点を当てます。これは、メモを取るアプリややることリストなどに広く適用できます。
リストとメモのドメインには、アプリにとって意味のある次のインテントがあります。
- タスクのリストを取得します。
- リストに新しいタスクを追加します。
Siriとのやり取りは実際にはアプリの外部で行われるため(アプリが実行されていない場合でも)、iOSは拡張機能を使用してこれを実装します。
インテント拡張
拡張機能を使用したことがない場合は、次の3つの主要なことを知っておく必要があります。
- 拡張機能は別のプロセスです。 アプリのバンドル内で配信されますが、独自のサンドボックスを使用して完全に単独で実行されます。
- アプリと拡張機能は、同じアプリグループに属することで相互に通信できます。 最も簡単な方法は、グループの共有サンドボックスフォルダーを使用することです(したがって、同じファイルをそこに置いた場合、それらのフォルダーは同じファイルの読み取りと書き込みを行うことができます)。
- 拡張機能には、独自のアプリID、プロファイル、および資格が必要です。
アプリに拡張機能を追加するには、まず開発者アカウントにログインし、[証明書、識別子、プロファイル]セクションに移動します。
Apple DeveloperAppアカウントデータの更新
Appleデベロッパアカウントでは、最初に行う必要があるのはアプリグループを作成することです。 「識別子」の下の「アプリグループ」セクションに移動し、1つ追加します。
group
で始まり、その後に通常の逆ドメインベースの識別子が続く必要があります。 プレフィックスが付いているため、残りの部分にはアプリの識別子を使用できます。
次に、このグループを使用してSiriを有効にするために、アプリのIDを更新する必要があります。
- 「アプリID」セクションに移動し、アプリのIDをクリックします。
- 「編集」ボタンをクリックします。
- アプリグループを有効にします(別の拡張機能で有効になっていない場合)。
- 次に、「編集」ボタンをクリックしてアプリグループを構成します。 以前からアプリグループを選択します。
- SiriKitを有効にします。
- 「完了」をクリックして保存します。
次に、拡張機能の新しいアプリIDを作成する必要があります。
- 同じ「アプリID」セクションで、新しいアプリIDを追加します。 これは、サフィックスが付いたアプリの識別子になります。 この名前はSwiftでモジュールの名前になり、実際のイン
Intents
と競合するため、Intents
としてインテントだけを使用しないでください。 - アプリグループに対してもこのアプリIDを有効にします(以前と同じようにグループを設定します)。
次に、Intents拡張機能の開発プロビジョニングプロファイルを作成し、アプリのプロビジョニングプロファイルを再生成します。 通常どおりにダウンロードしてインストールします。
プロファイルがインストールされたので、Xcodeに移動してアプリの資格を更新する必要があります。
Xcodeでアプリのエンタイトルメントを更新する
Xcodeに戻り、プロジェクトナビゲータでプロジェクトの名前を選択します。 次に、アプリのメインターゲットを選択し、[機能]タブに移動します。 そこに、Siriサポートをオンにするためのスイッチが表示されます。
リストのさらに下で、アプリグループをオンにして構成できます。
正しく設定されている場合は、アプリの.entitlements
ファイルに次のように表示されます。
これで、Intents拡張ターゲットをプロジェクトに追加する準備が整いました。
インテント拡張機能の追加
ついに拡張機能を追加する準備が整いました。 Xcodeで、「ファイル」→「新しいターゲット」を選択します。 このシートがポップアップします:
「インテント拡張」を選択し、「次へ」ボタンをクリックします。 次の画面に入力します。
製品名は、AppleデベロッパWebサイトのインテントアプリIDでサフィックスを付けたものと一致する必要があります。
インテントUI拡張機能を追加しないことを選択しています。 これについてはこの記事では取り上げていませんが、必要に応じて後で追加できます。 基本的に、それはあなた自身のブランディングと表示スタイルをSiriの視覚的な結果に入れる方法です。
完了すると、XcodeはSiri実装の開始部分として使用できるインテントハンドラークラスを作成します。
インテントハンドラー:解決、確認、処理
Xcodeは、私たちの出発点となる新しいターゲットを生成しました。
最初に行う必要があるのは、この新しいターゲットをアプリと同じアプリグループに設定することです。 以前と同様に、ターゲットの[機能]タブに移動し、アプリグループをオンにして、グループ名で構成します。 同じグループ内のアプリには、ファイルを相互に共有するために使用できるサンドボックスがあることを忘れないでください。 Siriリクエストがアプリに到達するためにこれが必要です。
List-o-Matには、グループドキュメントフォルダを返す機能があります。 共有ファイルの読み取りまたは書き込みを行う場合は常に使用する必要があります。
func documentsFolder() -> URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.app-o-mat.ListOMat") }
たとえば、リストを保存するときは、次を使用します。
func save(lists: Lists) { guard let docsDir = documentsFolder() else { fatalError("no docs dir") } let url = docsDir.appendingPathComponent(fileName, isDirectory: false) // Encode lists as JSON and save to url }
Intents拡張テンプレートは、 IntentHandler
という名前のファイルを、 IntentHandler.swift
という名前のクラスで作成しました。 また、拡張機能のplist
のインテントのエントリポイントになるように構成しました。
この同じplist
に、サポートするインテントを宣言するセクションが表示されます。 まず、リストの検索を可能にするINSearchForNotebookItemsIntent
という名前のリストから始めます。 IntentsSupported
の下の配列に追加します。
次に、 IntentHandler.swift
に移動し、その内容を次のコードに置き換えます。
import Intents class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() default: return nil } } }
handler
関数は、特定のインテントを処理するオブジェクトを取得するために呼び出されます。 このクラスのすべてのプロトコルを実装してself
を返すことができますが、各インテントを独自のクラスに配置して、より適切に整理された状態に保ちます。
いくつかの異なるクラスを用意する予定なので、それらの間で共有する必要のあるコードの共通の基本クラスをそれらに与えましょう。
class ListOMatIntentsHandler: NSObject { }
インテントフレームワークでは、 NSObject
から継承する必要があります。 後でいくつかのメソッドを入力します。
これから検索の実装を開始します。
class SearchItemsIntentHandler: ListOMatIntentsHandler, INSearchForNotebookItemsIntentHandling { }
インテントハンドラーを設定するには、3つの基本的な手順を実装する必要があります
- パラメータを解決します。
必要なパラメーターが指定されていることを確認し、完全に理解していないパラメーターを明確にします。 - リクエストが実行可能であることを確認します。
これは多くの場合オプションですが、各パラメーターが適切であることがわかっている場合でも、外部リソースへのアクセスが必要な場合や、その他の要件がある場合があります。 - リクエストを処理します。
要求されていることを行います。
最初に実装するインテントであるINSearchForNotebookItemsIntent
は、タスク検索として使用できます。 これで処理できるリクエストの種類は、「List-o-Matで食料品店のリストを表示する」または「List-o-Matで店のリストを表示する」です。
余談ですが、「List-o-Mat」は実際にはSiriKitアプリの悪い名前です。これは、Siriがアプリ内のハイフンに苦労しているためです。 幸いなことに、SiriKitを使用すると、別の名前を付けたり、発音を提供したりできます。 アプリのInfo.plist
に、次のセクションを追加します。
これにより、ユーザーは「list oh mat」と言うことができ、それを1つの単語(ハイフンなし)として理解できます。 画面上では理想的には見えませんが、それがないと、Siriは「リスト」と「マット」を別々の単語だと思って非常に混乱することがあります。
解決:パラメータを把握する
ノートブックアイテムの検索には、いくつかのパラメーターがあります。
- アイテムタイプ(タスク、タスクリスト、またはメモ)、
- アイテムのタイトル、
- アイテムの内容、
- 完了ステータス(タスクが完了とマークされているかどうかに関係なく)、
- 関連付けられている場所、
- 関連付けられている日付。
最初の2つだけが必要なので、それらの解決関数を作成する必要があります。 INSearchForNotebookItemsIntent
には、実装するためのメソッドがあります。
タスクリストの表示のみに関心があるため、それをアイテムタイプの解決にハードコードします。 SearchItemsIntentHandler
に、次を追加します。
func resolveItemType(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INNotebookItemTypeResolutionResult) -> Void) { completion(.success(with: .taskList)) }
したがって、ユーザーが何を言っても、タスクリストを検索します。 検索サポートを拡張したい場合は、Siriに元のフレーズからこれを理解させ、アイテムタイプが欠落している場合はcompletion(.needsValue())
を使用します。 または、タイトルに一致するものを確認して、タイトルから推測することもできます。 この場合、Siriがそれが何であるかを知っていれば成功で完了し、複数の可能性を試す場合はcompletion(.notRequired())
を使用します。
タイトルの解像度は少し注意が必要です。 私たちが望んでいるのは、Siriがあなたの言ったことと完全に一致するリストを見つけた場合にリストを使用することです。 よくわからない場合、または複数の可能性がある場合は、Siriにそれを理解するための支援を求めてもらいたい。 これを行うために、SiriKitは、次に何をしたいのかを表現できる一連の解決列挙型を提供します。
したがって、「食料品店」と言えば、Siriは完全に一致します。 しかし、「ストア」と言うと、Siriは一致するリストのメニューを表示します。
この関数から始めて、基本的な構造を示します。
func resolveTitle(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { guard let title = intent.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveListName(with: possibleLists, for: title, with: completion) }
ListOMatIntentsHandler
基本クラスにgetPossibleLists(for:)
とcompleteResolveListName(with:for:with:)
を実装します。
getPossibleLists(for:)
は、Siriから渡されたタイトルと実際のリスト名をあいまい一致させる必要があります。
public func getPossibleLists(for listName: INSpeakableString) -> [INSpeakableString] { var possibleLists = [INSpeakableString]() for l in loadLists() { if l.name.lowercased() == listName.spokenPhrase.lowercased() { return [INSpeakableString(spokenPhrase: l.name)] } if l.name.lowercased().contains(listName.spokenPhrase.lowercased()) || listName.spokenPhrase.lowercased() == "all" { possibleLists.append(INSpeakableString(spokenPhrase: l.name)) } } return possibleLists }
すべてのリストをループします。 完全に一致する場合はそれを返し、そうでない場合は一連の可能性を返します。 この関数では、ユーザーが言った単語がリスト名に含まれているかどうかを確認するだけです(つまり、非常に単純な一致です)。 これにより、「Grocery」は「GroceryStore」と一致します。 より高度なアルゴリズムは、同じように聞こえる単語に基づいて一致させようとする場合があります(たとえば、Soundexアルゴリズムを使用)。
completeResolveListName(with:for:with:)
は、この可能性のリストをどうするかを決定する責任があります。
public func completeResolveListName(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: possibleLists[0])) } else { completion(.confirmationRequired(with: possibleLists[0])) } default: completion(.disambiguation(with: possibleLists)) } }
完全に一致した場合は、Siriに成功したことを伝えます。 不正確な一致が1つあった場合は、それが正しいかどうかをユーザーに尋ねるようにSiriに指示します。
複数の一致があった場合は、 completion(.disambiguation(with: possibleLists))
を使用して、Siriにリストを表示し、ユーザーにリストを選択させるように指示します。
リクエストが何であるかがわかったので、全体を調べて、それを処理できることを確認する必要があります。
確認:すべての依存関係を確認します
この場合、すべてのパラメーターを解決すれば、いつでもリクエストを処理できます。 一般的なconfirm()
の実装では、外部サービスの可用性を確認したり、承認レベルを確認したりできます。
confirm()
はオプションであるため、何もできず、Siriは、解決されたパラメーターを使用してすべてのリクエストを処理できると想定します。 明確にするために、これを使用することができます:
func confirm(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { completion(INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil)) }
これは、私たちが何でも処理できることを意味します。
ハンドル:やる
最後のステップは、リクエストを処理することです。
func handle(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { guard let title = intent.title, let list = loadLists().filter({ $0.name.lowercased() == title.spokenPhrase.lowercased()}).first else { completion(INSearchForNotebookItemsIntentResponse(code: .failure, userActivity: nil)) return } let response = INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil) response.tasks = list.items.map { return INTask(title: INSpeakableString(spokenPhrase: $0.name), status: $0.done ? INTaskStatus.completed : INTaskStatus.notCompleted, taskType: INTaskType.notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: "\(list.name)\t\($0.name)") } completion(response) }
まず、タイトルに基づいてリストを見つけます。 この時点で、 resolveTitle
は完全に一致することをすでに確認しています。 ただし、問題が発生した場合でも、失敗を返すことができます。
障害が発生した場合、ユーザーアクティビティを渡すオプションがあります。 アプリがHandoffを使用していて、この正確なタイプのリクエストを処理する方法がある場合、Siriはアプリに延期して、そこでリクエストを試行する可能性があります。 音声のみのコンテキスト(たとえば、「Hey Siri」で開始した場合)ではこれは実行されません。また、他の場合に実行されることを保証するものではないため、期待しないでください。
これでテストの準備が整いました。 Xcodeのターゲットリストでインテント拡張を選択します。 ただし、実行する前に、スキームを編集してください。
これにより、クエリを直接提供する方法が表示されます。
上記のハイフンの問題のため、「ListOMat」を使用していることに注意してください。 幸いなことに、それは私のアプリの名前と同じように発音されるので、それほど問題にはならないはずです。
アプリに戻って、「食料品店」リストと「金物店」リストを作成しました。 Siriに「ストア」リストを要求すると、次のような曖昧性解消パスを通過します。
「食料品店」と言うと、完全に一致する結果が得られます。
Siri経由でアイテムを追加する
解決、確認、処理の基本概念がわかったので、リストにアイテムを追加するインテントをすばやく追加できます。
まず、 INAddTasksIntent
を拡張機能のplistに追加します。
次に、 IntentHandler
のhandle
関数を更新します。
override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() case is INAddTasksIntent: return AddItemsIntentHandler() default: return nil } }
新しいクラスのスタブを追加します。
class AddItemsIntentHandler: ListOMatIntentsHandler, INAddTasksIntentHandling { }
アイテムを追加するには、タイトルの代わりにターゲットタスクリストを使用することを除いて、検索に対して同様のresolve
が必要です。
func resolveTargetTaskList(for intent: INAddTasksIntent, with completion: @escaping (INTaskListResolutionResult) -> Void) { guard let title = intent.targetTaskList?.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveTaskList(with: possibleLists, for: title, with: completion) }
completeResolveTaskList
はcompleteResolveListName
と似ていますが、タイプが少し異なります(タスクリストのタイトルではなくタスクリスト)。
public func completeResolveTaskList(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INTaskListResolutionResult) -> Void) { let taskLists = possibleLists.map { return INTaskList(title: $0, tasks: [], groupName: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil) } switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: taskLists[0])) } else { completion(.confirmationRequired(with: taskLists[0])) } default: completion(.disambiguation(with: taskLists)) } }
曖昧性解消ロジックは同じで、まったく同じように動作します。 「店」と言うことは明確にする必要があり、「食料品店」と言うことは完全に一致します。
confirm
は未実装のままにし、デフォルトを受け入れます。 handle
の場合、リストにアイテムを追加して保存する必要があります。
func handle(intent: INAddTasksIntent, completion: @escaping (INAddTasksIntentResponse) -> Void) { var lists = loadLists() guard let taskList = intent.targetTaskList, let listIndex = lists.index(where: { $0.name.lowercased() == taskList.title.spokenPhrase.lowercased() }), let itemNames = intent.taskTitles, itemNames.count > 0 else { completion(INAddTasksIntentResponse(code: .failure, userActivity: nil)) return } // Get the list var list = lists[listIndex] // Add the items var addedTasks = [INTask]() for item in itemNames { list.addItem(name: item.spokenPhrase, at: list.items.count) addedTasks.append(INTask(title: item, status: .notCompleted, taskType: .notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil)) } // Save the new list lists[listIndex] = list save(lists: lists) // Respond with the added items let response = INAddTasksIntentResponse(code: .success, userActivity: nil) response.addedTasks = addedTasks completion(response) }
アイテムのリストとターゲットリストを取得します。 リストを検索してアイテムを追加します。 また、Siriが追加されたアイテムとともに表示する応答を準備し、それを完了関数に送信する必要があります。
この関数は、「ListOMatで、食料品リストにリンゴを追加する」などのフレーズを処理できます。 また、「米、玉ねぎ、オリーブ」などのアイテムのリストを処理することもできます。
ほぼ完了、ほんの少しの設定
これらはすべてシミュレーターまたはローカルデバイスで機能しますが、これを送信する場合は、 NSSiriUsageDescription
キーをアプリのplist
に追加し、Siriの使用目的を説明する文字列を追加する必要があります。 「リストに関するリクエストはSiriに送信されます」のようなもので問題ありません。
また、次の呼び出しを追加する必要があります。
INPreferences.requestSiriAuthorization { (status) in }
これをメインのViewControllerのviewDidLoad
に入れて、ユーザーにSiriアクセスを要求します。 This will show the message you configured above and also let the user know that they could be using Siri for this app.
Finally, you'll need to tell Siri what to tell the user if the user asks what your app can do, by providing some sample phrases:
- Create a
plist
file in your app (not the extension), namedAppIntentVocabulary.plist
. - Fill out the intents and phrases that you support.
There is no way to really know all of the phrases that Siri will use for an intent, but Apple does provide a few samples for each intent in its documentation. The sample phrases for task-list searching show us that Siri can understand “Show me all my notes on <appName>,” but I found other phrases by trial and error (for example, Siri understands what “lists” are too, not just notes).
概要
As you can see, adding Siri support to an app has a lot of steps, with a lot of configuration. But the code needed to handle the requests was fairly simple.
There are a lot of steps, but each one is small, and you might be familiar with a few of them if you have used extensions before.
Here is what you'll need to prepare for a new extension on Apple's developer website:
- Make an app ID for an Intents extension.
- Make an app group if you don't already have one.
- Use the app group in the app ID for the app and extension.
- Add Siri support to the app's ID.
- Regenerate the profiles and download them.
And here are the steps in Xcode for creating Siri's Intents extension:
- Add an Intents extension using the Xcode template.
- Update the entitlements of the app and extension to match the profiles (groups and Siri support).
- Add your intents to the extension's
plist
.
And you'll need to add code to do the following things:
- Use the app group sandbox to communicate between the app and extension.
- Add classes to support each intent with resolve, confirm and handle functions.
- Update the generated
IntentHandler
to use those classes. - Ask for Siri access somewhere in your app.
Finally, there are some Siri-specific configuration settings:
- Add the Siri support security string to your app's
plist
. - Add sample phrases to an
AppIntentVocabulary.plist
file in your app. - Run the intent target to test; edit the scheme to provide the phrase.
OK, that is a lot, but if your app fits one of Siri's domains, then users will expect that they can interact with it via voice. And because the competition for voice assistants is so good, we can only expect that WWDC 2018 will bring a bunch more domains and, hopefully, much better Siri.
参考文献
- “SiriKit,” Apple
The technical documentation contains the full list of domains and intents. - “Guides and Sample Code,” Apple
Includes code for many domains. - “Introducing SiriKit” (video, Safari only), WWDC 2016 Apple
- “What's New in SiriKit” (video, Safari only), WWDC 2017, Apple
Apple introduces lists and notes - “Lists and Notes,” Apple
The full list of lists and notes intents.