-
SiriおよびApple Intelligence向けの高度なApp Intent機能
App Intentに関する各種の高度なAPIを使用すると、Siriとのより洗練された連係が可能になります。ユーザーの声だけでさまざまな操作を行えるようにする方法、Apple Intelligenceのサポートでデバイス内のコンテンツを探す方法、Siriがアプリの状態を把握できるようにオンスクリーン認識のためのコンテキストを提供する方法を解説します。
関連する章
- 0:00 - Introduction
- 1:59 - Customize how Siri responds
- 4:20 - Visual responses
- 6:22 - Interaction donations
- 9:46 - Confirmations and entity ownership
- 11:59 - Semantic index with IndexedEntity
- 13:32 - Structured search with IntentValueQuery
- 15:27 - In-app search
- 16:22 - Onscreen awareness
- 20:51 - Leverage existing integrations
- 23:30 - Next steps
リソース
- App Intents Testing
- Donating your app’s data and actions to the system
- Donations and discovery
- Making app entities available in Spotlight
- Making actions and content discoverable by Apple Intelligence
- Providing contextual cues to Apple Intelligence and Siri
- Apple Intelligence and Siri AI
関連ビデオ
WWDC26
-
このビデオを検索
こんにちは Antonio Cancioです App Intentチームの ソフトウェアエンジニアです。 「SiriとApple Intelligenceのための App Intentの高度な機能の詳細」を 探っていきましょう。
本日は、いくつかのテクニックを 紹介します。 SiriとApple Intelligenceで アプリの体験を 基本レベルから高め、洗練され 個性的でオリジナリティあふれるものにする 方法です。 このセッションはApp Intentと App Schemasの基本理解を前提としています。 初めてご覧になる方は、 基本的なセッションから始めることを おすすめします。 基本事項をカバーしています。
本日のプランをご紹介します。 まず、Siriを通じたアプリとの会話を より直感的で なじみやすいものにする方法を 探っていきます。 アプリの外観と操作感に合わせた カスタムレスポンスの作り方や インタラクションの提供方法の 設定について学びます。 Apple Intelligenceをより パーソナルに感じさせるためのものです。
次に、コンテンツをより広く利用できるよう にする方法をご説明します。 セマンティックインデックスの統合から 構造化検索、アプリ内検索まで オンスクリーン認識を含め、Siriが コンテンツを見つけられるようにする 方法を習得できます。 画面に表示されているものと Siriが 理解できるアクションをつなぐ方法も学べます。
最後に、Siriはシステムのどこからでも 利用できます。 エンティティアノテーションを追加する 方法を紹介します。 通知との既存の連携に追加していきます。 Now PlayingやAlarmも対象です。 どこでコンテンツに出会っても 操作できるようになります。 実際にどう機能するかを示すために 同僚たちと構築してきた いくつかのアプリを紹介します。 CosmoTunesは音楽を再生できるアプリで 好きな曲でアラームや タイマーを設定できます。
UnicornChatは友達と連絡を取り合う ためのメッセージアプリです。 CometCalはカレンダーを 管理するためのアプリです。 ふむ。 App Intentには天体のシンボルを 使いたくなる何かがありますね。 これらのサンプルは下のリンクから ダウンロードして一緒に試せます。
まず、カスタムレスポンスを使ってSiriの 会話を形成することから始めましょう。
Siriが重要な処理を担います。 自然言語を理解し、適切な アクションを選択して 役に立つレスポンスを 作り出します。 App IntentフレームワークはSiriの 動作を制御するツールを提供し レスポンスを洗練させます。 レスポンスの方法をカスタマイズすることで アプリ独自の個性を 際立たせることができます。 コードで説明しましょう。 CosmoTunesの.addToPlaylistIntentは 曲をプレイリストに追加できます。 まず、Siriにレスポンスを 処理させたいと思います。 インテントのperformメソッドで 曲をプレイリストに追加し 空のIntentResultを返します。
これにより、インテント実行時の レスポンスをSiriに任せます。 試してみた後、レスポンスをアプリの 個性にもっと合わせたいと思いました。 私は曲を「tracks」、プレイリストを 「mix tapes」と呼んでいます。 カスタマイズするために、performメソッドに ダイアログレスポンスを提供するマークを付けます。 ProvidesDialogプロトコルを 追加することで行います。 IntentResultも変更し、 IntentDialogを渡します。 フル文字列とサポート文字列の 両方を含めます。 Siriはサポート文字列を UIと一緒に表示でき AirPodsのような音声のみのデバイスでは フルダイアログを読み上げます。 そのため、フル文字列は単独で何が 起きたかを説明する必要があります。 これでインテント完了時の レスポンスについて説明しました。 では、インテント実行中に質問したい 場合はどうすればいいでしょうか。 適切なタイミングでの確認質問により ユーザーが意図したアクションを 完了できるようになります。 インテントの結果の前に 質問するには performメソッド内で ダイアログリクエストを使います。
CosmoTunesでは音声再生を開始または 停止するタイマーを作成できます。 設定した時間が経過したら 動作します。 このインテントは必須パラメータと オプションのパラメータを持つ スキーマを採用しています。 すでに実行中のタイマーがある場合 混乱を避けるためにこの新しい タイマーの名前を付けるよう ユーザーに聞きたいと思います。
オプションのlabelパラメータの 値をリクエストします まだ指定されていない場合に。 リストから選択させたい場合や 確認を求めたい場合は サンプルアプリとドキュメントを ご覧ください。 他の種類のダイアログリクエストに ついて学べます。 次に、SiriのビジュアルをアプリのUIと 一致させたいと思います。 エンティティの表示表現と インテントレスポンスのカスタムビューは 情報を視覚的に提示する 優れた機会であり ダイアログと並べてアプリの アイデンティティを表現できます。
エンティティのDisplayRepresentation を定義することで コンテンツ表示時にエンティティが どのように見えて読まれるかを Siriに伝えられます。 エンティティの表示表現は レスポンスで使用できます。 エンティティが作成または 更新された場合などです。 類似するエンティティの選択を 求める際にも使われ アプリのコンテンツに関する 質問への回答にも使われます。 SpotlightやShortcutsも 利用できます。 基本的なDisplayRepresentationを 定義するには、タイトルを指定します。
サブタイトルと画像を追加することで さらにリッチにできます。 Siriが表示できる曲に関連した 画像を提供します。 アプリ内で曲について 質問したときに表示されます。 エンティティのDisplayRepresentationにより システム全体でのエンティティの 視覚的なアイデンティティを洗練できます。 システム全体で機能します。 特定のアクションのビジュアル レスポンスを定義するには カスタムビュースニペットを 使用できます。 AddToPlaylistIntentに戻ると Siriはすでにエンティティの表示表現を 使って自動的にレスポンスします。
カスタムビューを使うには performメソッドにShowsSnippetViewの 戻り値型を追加します。 これによりSwiftUIビューを含む IntentResultを返せるようになり PlaylistSnippetViewのような ビューで おなじみの色で プレイリストの詳細を表示できます。 カスタマイズに取り組む際は インテントをテストして どこにカスタマイズが 本当に意味をなすかを判断してください。 すべてのプラットフォームで正確で自然に 聞こえるレスポンスを確保してください。 AirPodsのような 音声のみのデバイスも含めて。 摩擦を避けるため、確認質問は 控えめに使ってください。 最後に、カスタムビジュアルを使ってアプリの アイデンティティをSiriに取り込み エコシステム全体でのスケールを 考慮してください。
インテントのレスポンスは通常、Siriの インタラクションの最後に表示されます。 しかし、Siriはインテントを呼び出す前に 追加の質問をすることもできます。 例えば、誰かにメッセージを送るよう 頼んだとすると Siriは類似した名前の連絡先の リストから選ぶよう求めるかもしれません。
システムがどちらを意図しているか 確信が持てないためです。 そこで登場するのが Interaction Donationsです。 Apple Intelligenceがそのような リクエストをさらにスマートに 処理できるよう採用できる APIです。
朗報があります。 SiriやShortcutsを通じてアプリを 操作すると システムはすでにそれを 把握しています。 しかし、Apple Intelligenceは アプリのUIを通じた ユーザーのアクションを あなたの助けなしには学べません。 そこでドネーションが活躍します。 各ドネーションは、ユーザーがアプリのUIで 特定のアクションを行ったというヒントです。 システムはこれらをスキーマに準拠した App Intentとして保存し 一時的なトランスクリプトに記録します。 Siriがよりスマートな判断をするために 必要なコンテキストを提供します。 トランスクリプトには時間をかけたUI インタラクションのドネーションが含まれます。 UnicornChatを開いて作成ビューから メッセージを送信したとします。 その瞬間、アプリはメッセージ送信 アクションをシステムにドネーションします。 SendMessageIntentスキーマを 使用して。 アプリ内で頻繁にメッセージを 送るようになると 最終的に誰かがこう言ったとき 「ホーム画面から連絡先に メッセージを送って」 Siriはその連絡先に使う 適切なアプリを推測するかもしれません。 メッセージ送信のInteraction Donationsを採用するために UnicornChatのConversationViewを 調べることから始めました。 ConversationViewと sendMessageインテントは どちらも同じヘルパーを呼び出して メッセージを送信します。 これがsendMessageインテントの ドネーションを始める良いポイントに思えます。 donateIntentパラメータを 追加します。 ヘルパーがインテントから呼ばれたか UIから呼ばれたかを 判別するためです。 Apple IntelligenceはすでにSiriの インタラクションから学んでいるため UIのインタラクションだけを ドネーションすれば大丈夫です。
次に、インテントを作成してパラメータと インテントの結果を入力し IntentDonationManager APIを通じて ドネーションします。 これで、ユーザーがアプリを開いて メッセージを送ると システムはUnicornChatを いつ好んで使うかを学習できます。
設定の学習だけでなく Interaction DonationsはSiriに アプリ内の進行中のアクティビティを 把握させておくこともできます。 これはSiriで開始または停止できる アクティビティに特に役立ちます。 Mapsドメインのアプリでは アプリのUIでNavigationSessionを 開始できます。 そのインタラクションが ドネーションされます。 そして、車に乗ってSiriに 途中で立ち寄り場所の追加を頼みます。 Interaction Donationのおかげで Siriはアプリでどの NavigationSessionがアクティブかを 把握でき ユーザーのリクエストを サポートできます。 このパターンは、NavigationSessionを 開始または 停止するインテントに 適用されます(Mapsドメイン)。 Clockドメインではストップウォッチの停止、 開始、一時停止、ラップにも適用されます。
Interaction Donationsはアプリ内の 実際のユーザー行動を 正確に反映する必要があります。 アプリが過剰にドネーションすると システムはそれらを無視することがあります。 Siriがすべてのパラメータ値を 収集し App Intentを呼び出す準備ができたら 最後のステップ「確認」があります。
アクションが正しいかどうかを 確認するよう求めることで ユーザーに情報を提供し 意図しない副作用から保護します。 これはLarge Language Modelsにおける 既知のリスクです。
これは、データに意味のある副作用をもたらす 可能性のあるインテントで特に重要です。 または外部の世界への影響でも。 だからこそSiriはこのような インテントを自動的に確認できます。 例えば、こう言ったときに 確認を求めます。 「CometCalで来週の探検を キャンセルして」
これは、アプリのコンテンツを 更新するインテントでより重要です。 ユーザーが公開または 他の人と共有している場合です。
例えば、個人イベントを更新する際は Siriが確認しないかもしれませんが Crew Lunchの更新を頼むと 確認するかもしれません。 参加者のいるイベントを 更新するためです。 デフォルトでは、Siriはエンティティが ユーザーのプライベートなものと想定し それらの確認をスキップする 場合があります。
オーナーがエンティティを公開したり他の人と 共有したりしたことをSiriに伝えるには 関連するエンティティを新しい OwnershipProvidingEntityプロトコルに 準拠させます。プロトコルは エンティティにのみ追加してください。 アプリ内で共有または公開できる エンティティに限ります。 次に、オーナーシップの状態を 提供します。 オーナーシップの状態を 最新に保ってください。 システムがアプリからエンティティを リクエストするたびに。 これにより、確認するかどうかを判断する際に Siriに必要な情報が確保されます。
先ほどカスタマイズした エンティティの 表示表現を覚えていますか。 Siriはこれらのインテント確認でも ビジュアルとして使用できます。 適切なタイミングでアクションを 確認する機会を提供することで SiriとのアプリへのApp体験に 対する信頼が高まります。 信頼を確立しリスクを軽減する その他の方法について詳しくは 「Secure your app: Mitigate risks to agentic features」をご覧ください。
これまで、これらのアプリのアクションが Siriでどのように機能するかを定義し Apple Intelligenceにユーザーの アプリ使用状況のコンテキストを提供し 意図しない副作用からユーザーを 保護するようSiriを設定しました。 次に、Siriがコンテンツを最初から どのように見つけるかについて説明します。 取り上げたい3つのパスがあります。 セマンティックインデックス、 構造化検索、そしてアプリ内検索です。 CosmoTunesのプレイリストはデバイス上に すべてローカルに保存されています。 Apple Intelligenceが 見つけやすくするために IndexedEntityを採用し、Spotlightで エンティティをインデックスします。 使用するのはCSSearchableIndexの .indexAppEntitiesメソッドです。 これによりSpotlightのセマンティック インデックスが入力されます。
これでSiriに聞けます。「CosmoTunes でWWDCプレイリストを再生して」
Spotlight検索UIでプレイリストを 検索することもできます。 App Intentドメインによっては Spotlightでエンティティをインデックス することでセマンティック検索機能が提供されます。 つまりApple IntelligenceとSiriは 意味に基づいてエンティティを理解できます。 完全一致のキーワードだけでなく。
インデックスへの追加が 最初のステップです。 最新の状態に保つことが Siriが コンテンツを見つける上で重要です。 ユーザーがコンテンツを追加したら 新しいエンティティをインデックスします。 主要なプロパティが変更されたら 既存のエントリを更新します。 特に表示表現で使用されている プロパティは重要です。
ユーザーがコンテンツを削除したら インデックスのエントリも削除します。 Spotlightがアプリにエンティティの 再インデックスを求める場合があります。 新しいIndexedEntityQueryを採用することで アプリは再インデックスをサポートできます。 サンプルプロジェクトで IndexedEntityQueryをご確認ください。 Core SpotlightレベルのAPIで 再インデックスをすでにサポートしている場合は IndexedEntityQueryを定義する 必要はありません。
ただし、コンテンツデータセットが大きい場合や サーバー上にある場合、 または頻繁に変更されて 事前インデックスが難しい場合は エンティティをインデックスしない ことがあります。 例えば、アプリのプレイリストはすべて インデックスしますが、曲はしないことにしました。 それでもSiriで曲を再生する 柔軟性を提供するために IntentValueQueryを 使用しました。
IntentValueQueryはすべてのエンティティを 事前にインデックスしない場合に適しています。 EntityQueryと 非常に似ています。 主な違いとして アプリがシステムから構造化された 検索入力を受け取り 複数のエンティティタイプを 返せることが挙げられます。 SiriにはCosmoTunesの PlayAudioIntentの audioEntityパラメータに対応する エンティティが必要です。
エンティティを見つけるために、 SiriはAudioSearchと一緒に IntentValueQueryを呼び出します。
クエリはその検索入力の 構造化されたプロパティを アプリのオーディオエンティティに マップします。 IntentValueQueryではvalues for メソッドを実装して AudioSearch入力を処理し AudioEntityを返します。 AudioEntityは曲とプレイリストの 両方を含むUnionValue型です。 AudioSearchの値には .criteriaプロパティがあり ユーザーのクエリを 表します。 .searchQueryケースにはユーザーが言った 内容の関連部分が含まれており それを使って一致するエンティティを 見つけます。 アプリは未指定の検索も サポートしています。 例えば「CosmoTunesを再生して」 何を再生したいか 具体的でない場合です。 その場合、アプリは以前にいいねした 曲をすぐに再生し始めます。
アプリからのリンクを参照する場合の URLケースもあります。 例えば「Glowが送ってくれた プレイリストを再生して」
AudioSearch criteriaの完全なセット についてはドキュメントをご確認ください。
Siriにアクションを求めているのではなく 何かを探したいだけの 場合もあります。 「CosmoTunesでランニング用の プレイリストを見せて」と聞くと Siriはエンティティ検索結果の リストを表示できます。 それも良いデフォルトです。 しかし、アプリ独自の検索体験を 丁寧に作り込んできたので そこで結果を表示したいと 思いました。
そのために、system .searchInApp スキーマを採用します。
iOS 17で導入された.systemの 検索スキーマは .system.searchInAppという 名前になりました。 System App Schemaドメインの 一部です。 Siriでアプリ内を検索する 機能を提供します。 採用している他のドメインに関係なく エンティティをインデックスしていなくても 利用できます。
Siriはインテントを検索した 文字列と同じ文字列で呼び出し インテントのperformメソッドが アプリ内で結果を見つけて表示します。 Spotlightと構造化検索により、Siriは コンテンツを推論できます。 名前でコンテンツの再生をSiriに 頼む場合には優れた機能ですが 日常の会話と同様に、ユーザーは 目に見えるものを参照することが多いです。
だからこそ、画面上で見ている オーディオコンテンツを 操作できるようにしたいと 思います。
オンスクリーン認識とは、アプリが 画面上に表示されているものを システムが理解できる構造化情報と アクションに結びつける方法です。 Siriは「3番目のものを再生して」や 「あの会話」のような参照を解決できます。 ユーザーが明示的に 名前を言わなくても。 Siriリクエストを始めると、Siriは 画面上のテキストを把握していますが それはピクセルに表示されている 内容に限られます。 例えば、Siriは表示されている 曲を操作できず アーティストについて 教えられないかもしれません。 アーティストが現在 画面に表示されていないためです。
オンスクリーン認識APIを 採用すると 画面上にあるエンティティの 追加コンテキストをSiriに提供し 画面上のどこにあるかも 伝えられます。
このオンスクリーンコンテキストにより Siriはこれらのエンティティについて 詳細な質問に答え それらに対して アクションを実行できます。 オンスクリーン認識を採用する際は NSUserActivityとView Annotation APIが 出発点です。 そこから始めてください。 NSUserActivityでは、主要な オンスクリーンコンテンツを 表すビューに.userActivityを 付けます。 View Entityアノテーションは エンティティが画面上の多くの アイテムの1つである場合に使います。 エンティティを表す各ビューに .appEntityIdentifierを付けます。 CosmoTunesでは、AlbumViewが View Entityアノテーションを使います。 アルバムと含まれるトラックの 両方が表示されているためです。 NowPlayingViewはNSUserActivityを 使います。 画面が現在再生中のアイテムに 専念しているためです。 NSUserActivityと View Entityアノテーションは 画面にエンティティが少数ある場合に 十分です。 しかし、さらに2つの オンスクリーン認識APIがあります。 1つ目はリストやコレクション用で 多くのエンティティを一度に表示する場合です。
CosmoTunesのトラックは、アプリの いくつかのビューでリスト表示されています。
コレクションアノテーションは オーバーヘッドを避けるのに役立ちます。 すべての行に個別にアノテーションを 付ける手間を省きます。 代わりに、システムは必要に応じて 識別子を遅延フェッチします。 コレクションアノテーションにより Siriは選択されてスクロールで 画面外に出たエンティティも 検出できます。 行ごとのアノテーションは、ビューが ビュー階層から外れるとすぐに消えます。 SwiftUIでは、Listに対して .appEntityIdentifier(forSelectionType:) モディファイアを使います。 各アイテムの選択IDに対応する EntityIdentifierを返します。
2つ目のAPIはカスタムキャンバスビュー アノテーションです。 ピアノロールのような外観の カスタムキャンバスビューを作りました。 現在のトラックのノートを 視覚化しており CosmoTunes独自のレトロな 外観を演出しています。 このキャンバスが表示されているときは いつでも Siriで関連する曲を 操作できるようにしたいと思います。 この非標準のサブビューを システムに理解させるために カスタムキャンバスビューアノテーションを 使用しました。 SwiftUIを使っている場合は、CosmoTunesの サンプルコードのPianoRollViewで 採用方法をご確認ください。CosmoTunes のサンプルコードで確認できます。
UIKitとAppKitもオンスクリーン認識 APIをすべてサポートしています。 ドキュメントでAppEntityAnnotatableを ご確認ください。 UICollectionViewAppIntentsDataSourceと appEntityUIElementProviderもあります。 詳細については これらのエンティティアノテーションが UIKitアプリのコンテキストメニュー項目に どう役立つかについて「Modernize your UIKit app」をご確認ください。
オンスクリーン認識を採用すると アプリのビューの一部が 多くのエンティティを一度に表示します。
Siriは迅速に把握する 必要があります。 画面上のエンティティが リクエストに関連するかどうかを。 例えば、Siriに「3番目のものを 再生して」と頼んだとします。 画面上のエンティティを 十分な速さで理解できなければ Siriは確認を求めるか まったく別のものを 再生するかもしれません。
そうなるとリクエストを 諦める人もいます。 先ほどカスタマイズしたエンティティの 表示表現が役立ちます。
CosmoTunesではプレイリスト エンティティクエリで 表示表現クエリを 有効にしました。 displayRepresentationsメソッドを 実装することで。
これにより、Siriが画面上の コンテンツを理解しようとするとき エンティティのテキスト表現だけを クエリして データベースからコンテンツ全体を フェッチするオーバーヘッドを省けます。 オンスクリーン認識はSiriに 追加のコンテキストを提供します。 ユーザーがアプリを 見ているときに。 UIを超えて、アプリはすでにシステムの 他の部分と密接に連携しています。 Siriにさらに多くのコンテキストを 提供するために すでに採用している連携に エンティティを接続できます。 ユーザー通知などです。 この追加のコンテキストによりアプリの エンティティは共通言語として機能します。 Siriが画面上のものだけでなく 他のシステム連携が コンテンツにどう関連するかも 理解できるようになります。 アプリがすでに使っている3つの連携に エンティティを追加します。 UserNotifications、NowPlaying、 そしてAlarmKitです。
完了したら「ライブバージョンを 再生して」と言えるようになります。 現在再生中の曲の別バージョンに 簡単に切り替えられます。
UnicornChatの通知が AirPodsで読み上げられているとき
「返信、了解です、 ユニコーンサプライストアに 立ち寄って買ってきます」と言えます。
CosmoTunesのアラームを スヌーズするには「Snooze it」と言います。 3つとも同じパターンを使い これを エンティティアノテーションと呼びます。
通知にエンティティのアノテーションを 付けるとAirPodsで通知を読み上げる際に Siriに具体的なエンティティの コンテキストを提供します。 読み上げられた通知を 聞いているとき ユーザーはその背後にある エンティティを操作したいかもしれません。 メッセージに返信したり リマインダーをチェックしたりするなどです。 Siriに追加のコンテキストを 提供するために UnicornChatの通知に 関連するエンティティについて 投稿フローを更新します。 AppIntentsをインポートした後 永続的なメッセージのEntityIdentifierを UNMutableNotificationContentの .appEntityIdentifiersプロパティに 割り当てます。なお、説明している 3つのエンティティアノテーションAPIでは TransientAppEntityは 使えません。 Transientエンティティは 一時的なモデルオブジェクトであり 永続的な識別子を 持たないためです。 CosmoTunesのNowPlayingに エンティティアノテーションを追加するには 同じパターンに従います。 すでにMusicContentを使って 曲の属性を提供しており アプリのMediaSessionRepresentable 準拠で行っています。 この状態を改善するために 既存の曲 アーティスト、プレイリストの エンティティを取り appEntityIdentifiers プロパティに追加します。 より具体的なものから より一般的なものの順で。 これにより「ライブバージョンを再生して」 のようなコンテキストリクエストが 可能になります。AlarmKitでは 1つのEntityIdentifierを追加します。 AlarmConfigurationの appEntityIdentifierパラメータに アラームやタイマーを 作成するときに。 これで、鳴っているアラームや タイマーを操作できるようになります。 それだけです。 エンティティを通知、Now Playing、 アラームに接続するのに必要なのはそれだけです。
Siriでアプリをより効果的に動作させる いくつかの高度な方法を紹介しました。 次のステップを考えるにあたって まずエンティティの表示表現をカスタ マイズすることから始めると良いでしょう。 システム全体でエンティティの 表示に使われます。 そこから、エンティティを セマンティックインデックスに追加し インデックスを最新の状態に保つと Siriが常に最新のコンテンツを見つけられます。
IntentValueQueryとアプリ内検索で Siriからエンティティに アクセスできるようにすることも 検討してください。 そして、ビューやアクティビティ 既存のシステム連携に エンティティのアノテーションを付けると Apple Intelligenceにさらに多くの コンテキストを提供できます。
準備ができたら、UIインタラクションの ドネーションを検討してください。 Apple Intelligenceを助けるために ユーザーのアプリ使用方法を理解させ よりパーソナライズされた体験を実現できます。 これらのコンセプトの実際の適用を見るには サンプルプロジェクトをご確認ください。
App Schemasの採用を 実際に試すには 「Code-Along: Make your app available to Siri」をご確認ください。 27のリリースにより、Apple Intelligenceは Siriの可能性を変革しており App Intentはその変革的な力を 直接あなたの手に渡します。 魅力的で質の高い体験を構築するために 必要なものすべてが揃いました。 システムの自然な拡張のように 感じられる体験です。 皆さんの作品を 楽しみにしています。 またお会いしましょう。
-
-
2:42 - Custom dialog response
@AppIntent(schema: .audio.addToPlaylist) struct AddToPlaylistIntent { func perform() async throws -> some IntentResult & ProvidesDialog { // Adds song to playlist and responds return .result( dialog: IntentDialog( full: """ Added \(song.title) to the \ \(playlist.title) mix tape. """, supporting: "Added" ) ) } } -
3:42 - Ask a clarifying question within an inten
@AppIntent(schema: .clock.createTimer) struct CreateTimerIntent { // MARK: Schema Parameters var duration: Duration var label: String? var isSleepTimer: Bool func perform() async throws -> some ReturnsValue<TimerEntity> { // Checks active timers and requests label parameter label = try await $label.requestValue( """ You already have a timer running. \ What should we call this one? """ ) return .result(value: timerEntity) } } -
4:26 - Enhanced DisplayRepresentation
// Enhanced DisplayRepresentation @AppEntity(schema: .audio.song) struct SongEntity { var displayRepresentation: DisplayRepresentation { DisplayRepresentation( title: "\(title)", subtitle: "\(artistName)", image: artworkImage ) } } -
5:05 - Return a custom snippet view
@AppIntent(schema: .audio.addToPlaylist) struct AddToPlaylistIntent { var audioEntity: AudioEntity var playlist: PlaylistEntity func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView { // Adds to playlist and shows dialog and snippet let view = PlaylistSnippetView( playlist: updatedEntity, tracks: updated.tracks ) return .result(dialog: dialog, view: view) } } -
7:44 - Donate a UI interaction
@ModelActor actor ModelManager { func sendMessage(_ /* ... */, donateIntent: Bool = false) async throws -> [Message.ID] { // Donate intent with parameters and result so Siri can learn user preferences if donateIntent { let intent = SendMessageIntent() intent.destination = .recipients(conversation.recipients.map(\.entity)) let result = messages.map(\.entity) Task { try await IntentDonationManager.shared.donate( intent: intent, result: .result(value: result) ) } } } } -
10:03 - Declare entity ownership for confirmations
// Informs system if entity is public or shared with others @AppEntity(schema: .calendar.event) struct EventEntity: OwnershipProvidingEntity { var ownership: EntityOwnership { // isShared used to compute ownership state: .shared, .public, or .unknown attendees.isEmpty ? .unknown : .shared } } -
11:30 - Index entities with IndexedEntity
// Indexing IndexedEntity with CSSearchableIndex struct EntityIndexingHelper { // Indexes playlist entities func indexPlaylist(_ playlist: Playlist) async throws { let entity = PlaylistEntity(playlist: playlist) try await CSSearchableIndex(name: indexName) .indexAppEntities([entity]) } } -
13:38 - Structured search with IntentValueQuer
// Structured search of songs and playlists struct AudioIntentValueQuery: IntentValueQuery { // AudioSearch, IntentPerson, and other system types may be supported as input func values(for input: AudioSearch) async throws -> [AudioEntity] { switch input.criteria { case .searchQuery(let query): return try await searchResults(for: query) case .unspecified: return try await likedSongResults() // ... also a .url case } } } -
14:49 - Re-run Siri search in your app
// Intent that re-runs the Siri search in app @AppIntent(schema: .system.searchInApp) struct SearchAudioLibraryIntent { var criteria: StringSearchCriteria func perform() async throws -> some IntentResult { // Perform in-app search with Siri search string navigation.searchText = criteria.term navigation.selectedTab = .library return .result() } } -
16:27 - Onscreen awareness annotations
// (a) Single primary entity on screen — NSUserActivity struct NowPlayingView: View { @Environment(PlaybackController.self) private var playback var body: some View { VStack { // Player UI } .userActivity("cosmotunes.nowPlaying", isActive: playback.currentTrack) { activity in activity.title = playback.currentTrack?.title activity.appEntityIdentifier = EntityIdentifier( for: SongEntity.self, identifier: playback.currentTrack.id ) } } } // (b) One entity among many — View Entity annotation struct AlbumView: View { private var header: some View { VStack(alignment: .leading, spacing: 6) { // ... } .appEntityIdentifier( EntityIdentifier(for: AlbumEntity.self, identifier: session.id.uuidString) ) } } // (c) Lists and collections — Collection annotation struct PlaylistDetailView: View { var body: some View { List { ForEach(playlist.tracks) { track in PlaylistTrackRow(track: track) } } .appEntityIdentifier(forSelectionType: GeneratedTrack.ID.self) { trackID in EntityIdentifier(for: SongEntity.self, identifier: trackID) } } } -
17:23 - Component-based display representation query
// Component-based display representation queries extension PlaylistQuery { func displayRepresentations( for identifiers: [PlaylistEntity.ID], requestedComponents: DisplayRepresentation.Components = .text ) async throws -> [PlaylistEntity.ID: DisplayRepresentation] { let entities = try await model.playlistEntities(for: identifiers) // Fetch display representations for fetched entities var result: [PlaylistEntity.ID: DisplayRepresentation] = [:] for entity in entities { result[entity.id] = await entity.displayRepresentation(with: requestedComponents) } return result } } -
21:07 - Entity annotations on system integrations
// (a) User notifications import AppIntents import UserNotifications func scheduleNotification(message: Message, author: Contact, conversation: Conversation) { let content = UNMutableNotificationContent() content.title = author.name content.body = message.body // Annotate with entity identifier content.appEntityIdentifiers = [ EntityIdentifier(for: MessageEntity.self, identifier: message.id) ] // Schedule the notification } // (b) Now Playing — most specific to least specific import NowPlaying final class CosmoTunesMediaSession: MediaSessionRepresentable { var content: (any MediaContentRepresentable)? { var content = MusicContent(id: track.id.uuidString, songTitle: track.title /* ... */) content.appEntityIdentifiers = [ EntityIdentifier(for: SongEntity.self, identifier: track.id), EntityIdentifier(for: ArtistEntity.self, identifier: track.session.artistName), EntityIdentifier(for: PlaylistEntity.self, identifier: currentPlaylist.id), ] return content } } // (c) AlarmKit import AlarmKit func scheduleAlarm(_ alarm: Alarm) async throws { let configuration = AlarmManager.AlarmConfiguration<CosmoTunesAlarmMetadata>.alarm( schedule: schedule, attributes: attributes, appEntityIdentifier: EntityIdentifier(for: AlarmEntity.self, identifier: alarm.id), stopIntent: DismissAlarmIntent(), secondaryIntent: SnoozeAlarmIntent(), sound: sound ) // Schedule alarm }
-
-
- 0:00 - Introduction
Advanced App Intents techniques to make your app's Siri and Apple Intelligence experience feel polished and personal. Agenda: shape the Siri conversation, improve content discovery, and leverage existing integrations, demoed with the CosmoTunes, UnicornChat, and CometCal sample apps.
- 1:59 - Customize how Siri responds
Shape Siri's responses to match your app's voice: return an empty result to let Siri respond, or adopt ProvidesDialog and return an IntentDialog with full and supporting strings. Ask clarifying questions mid-intent with a dialog request (such as requesting an optional timer label).
- 4:20 - Visual responses
Give Siri your app's look: an entity's DisplayRepresentation (title, subtitle, image) is used across responses, disambiguation, Spotlight, and Shortcuts, while a custom SwiftUI snippet view (ShowsSnippetView) styles specific actions. Customize only where it helps, and account for voice-only devices.
- 6:22 - Interaction donations
System interactions are known automatically, but UI interactions aren't, so donate them via IntentDonationManager (using schema-conforming intents) so Apple Intelligence learns app preferences and stays aware of ongoing activities (such as Maps navigation or Clock stopwatches). Donate accurately; excessive donations are ignored.
- 9:46 - Confirmations and entity ownership
Siri auto-confirms intents with meaningful side effects, especially on shared or public content. Conform shareable entities to the new OwnershipProvidingEntity protocol and keep the ownership state current so Siri confirms appropriately, using your display representations as the confirmation visuals.
- 11:59 - Semantic index with IndexedEntity
Make local content discoverable: adopt IndexedEntity and index entities in Spotlight via indexAppEntities for meaning-based search. Keep the index fresh (add, update, delete), and support re-indexing with the new IndexedEntityQuery.
- 13:32 - Structured search with IntentValueQuery
For content too large, server-side, or fast-changing to index, use IntentValueQuery: the system passes a structured search input and you can return multiple entity types. CosmoTunes maps an AudioSearch (query, unspecified, or URL criteria) to a UnionValue of songs and playlists.
- 15:27 - In-app search
Adopt the system searchInApp schema (formerly system.search) so "Show me running playlists in CosmoTunes" re-runs Siri's search inside your own crafted search UI, regardless of which domains you adopt or whether you index entities.
- 16:22 - Onscreen awareness
Connect what's visible to entities so Siri resolves "play the third one." Start with NSUserActivity (single primary item) and View Entity annotations (appEntityIdentifier, one of many); scale up with collection annotations (forSelectionType:) and custom canvas annotations, all supported in UIKit and AppKit. Enable display-representation queries so Siri resolves on-screen entities fast.
- 20:51 - Leverage existing integrations
Attach entities to system integrations you already use: appEntityIdentifiers on UNMutableNotificationContent (reply to announced notifications), on Now Playing via MediaSessionRepresentable (for example "play the live version"), and appEntityIdentifier on AlarmKit's AlarmConfiguration ("snooze it"). Persistent entities only, no transient entities.
- 23:30 - Next steps
Start by customizing entity display representations, then index entities and keep the index current, add IntentValueQuery and in-app search, annotate views and existing integrations, and finally donate UI interactions. See the sample projects and "Code-along: Make your app available to Siri."