
-
App Intentの基礎知識
App Intentフレームワークの概要と、Appleのデベロッパプラットフォームにおいてますます重要性が高まっているこのフレームワークの役割について学びましょう。このセッションでは、インテント、エンティティ、クエリなどのコア概念を基本から解説します。これらの要素が連携することで、Spotlightやショートカットなどのソフトウェア機能とアクションボタンなどのハードウェア機能を実現するための、Appleデバイスを通じたアプリとApp Intentの統合が可能になります。今後Apple Intelligenceをアプリに統合する際のゲートウェイとしてApp Intentを活用する方法についても解説します。
関連する章
- 0:00 - イントロダクション
- 0:45 - App Intentエコシステム
- 2:47 - フレームワークの紹介
- 21:15 - 仕組み
リソース
- Accelerating app interactions with App Intents
- Adopting App Intents to support system experiences
- App intent domains
- App Intents
- App Shortcuts
- Building a workout app for iPhone and iPad
- Creating your first app intent
- Integrating actions with Siri and Apple Intelligence
- Making actions and content discoverable and widely available
関連ビデオ
WWDC25
WWDC24
-
このビデオを検索
Swift Intelligence Frameworksチームの エンジニアリングマネージャー Jamesです App Intentについて 説明できることをうれしく思います Apple Intentは システム全体 そしてすべてのAppleプラットフォームでの アプリの見つけやすさや可視性 また機能を向上させるための フレームワークです 最初に ますます重要になっている Appleデベロッパエコシステムにおける Apple Intentの役割について 説明します
次に フレームワークを使い システムを通じて アプリのアクションやエンティティを ユーザーに提供する方法について説明します 最後にApple Intentを記述する際に 知っておいた方がよい いくつかの情報を提供します
App Intentの使い方を説明する前に 「なぜApp Intentなのか」を説明します App Intentは アプリケーションに 機能を構築するためだけの フレームワークではありません アプリの機能をシステム全体に 拡張するための エコシステムです
App Intentを使用することで Spotlightでカスタマイズした結果を提供したり アクションボタンにコンテキストに応じた エクスペリエンスを提供したり ウィジェットに構成機能や インタラクティブな機能を提供したり コントロールセンターのコントロールに 簡単にアクセスできるようにしたりできます Apple Pencil Proに カスタムアクションも設定できます
今年からSpotlightを使って アプリのアクションをMac上のどこからでも 呼び出せるようになりました
App Intentフレームワークを使用すると ユーザーがアプリを開いていなくても これらの豊富な機能を届けることができます まずは アプリで実行可能なアクション 例えばメモを開いたりワークアウトを開始したり 買い物リストにアイテムを追加したり といったアクションから始まります これらはいわばアプリの動詞で もう想像がついているかもしれませんが App Intent 略してインテントを作成することで それらを記述することができます インテントを作成する時は アクションが正常に実行されるように システムに追加情報を提供します インテントはパラメータと戻り値を 受け取ることができます これらの入力と出力は Swiftネイティブの型または アプリ内で定義された型にすることができます App Intentでは 2種類の値を作成できます 定数の値のセットを持つ型には AppEnumを使用します ダイナミックタイプには AppEntityを使用します AppEnumとAppEntityは いわばアプリの名詞です アプリショートカットは 重要なインテントをさらに高めて それらをよりアクセスしやすく また見つけやすくします アプリショートカットは Spotlightでの検索やSiriの使用 アクションボタンの構成などの際に 表示されます これらはいわばアプリの文と みなすことができ インテントとそれを動かすために 必要なパラメータでできています 学習するのに最も優れた方法は 実際にやってみることです 私の最初のインテントを作成するために 何が必要だったかを見てみましょう 私は家族と一緒に旅行するのが好きで 世界中の有名な観光名所を チェックするためのアプリを 開発しています アプリにはいくつかのセクションがあります 有名な観光名所の リストをスクロールして 地図上でチェックしたり 作成したコレクションを表示したりできます 多くのユーザーは観光名所のグリッドを スクロールして楽しんでいます そこでApp Intentを作成して アプリのこの部分にもっと簡単に 直接移動できるようにしようと思います どうすればよいでしょうか 最初に App Intentプロトコルを採用した 新しい構造体を定義します App Intentの最小要件は タイトルとperformメソッドです
タイトルは ローカライズされた一意の文字列で インテントの名前として 表示されます performメソッドには インテントのロジックが含まれています 共有ナビゲータを使用して 観光名所ビューを開きます ナビゲーションはメインスレッドで 行う必要があります そのため @MainActorでperformをマークします
performメソッドは IntentResultを返します IntentResultには Siriが話すことができるダイアログや 表示するビュースニペットなど 色々なものを含めることができます デフォルトではインテントを実行しても アプリはフォアグラウンドで実行されません そのため ダイアログやスニペットは アクションの結果を表示する優れた方法です
このインテントはアプリ内の画面に 移動できるように設計されているため 実行時にアプリが開くように インテントを設定します 新しいsupportedModesプロパティを foregroundに設定し インテントが実行される前に アプリが開くようにします 最初のインテントに必要なことは 以上です アプリをインストールしたら インテントはショートカットにあります 新しいショートカットを作成し 先ほどのナビゲーションインテントを追加します 実行するとアプリがフォアグラウンドで実行され 観光名所ビューに直接移動します 私のアプリには コレクションを表示したり 観光名所を地図上に表示したりするための セクションもあります インテントによってアプリのそれらの セクションにも移動できたらいいですね 私のアプリでは単純なSwiftの列挙を使用して セクションをモデル化しています この型をフレームワークと 互換性のあるものにするため AppEnumプロトコルを採用します AppEnumには いくつかの要件しかありません 文字列からインスタンス化できる必要があるので Stringの生の値を追加します TypeDisplayRepresentationで 型全体を記述します caseDisplayRepresentationで 列挙の各ケースを記述します
この情報はコンパイル時に 使用されるため これらの表現は 定数値である必要があります
ここでNavigationOptionを保持するための 新しい変数を追加します これをインテントパラメータにするため @Parameter属性を追加します インテントパラメータは インテントの入力となり 必須またはオプションにすることができます ここでは必須にしました そのため 実行時にperformを呼び出す前に パラメータに値があることが確認されます
解決されたNavigationOptionを 使用するようにperformメソッドを書き換えます そして新しいアクションに合わせて タイトルを変更します
ショートカットに戻ると Navigation Optionが 編集可能なパラメータとして表示されます インテントを実行すると セクションを入力するように求められます 「Map」を選択すると 該当するビューが直接開きます App Intentフレームワークの型は 詳細にカスタマイズできるようになっています このため 構成要素をすばやく配置して 機能を改良することができます ここで使い勝手をよくするために いくらかの追加情報を インテントに追加します
デフォルトではショートカットを開くと 各パラメータが行として表示されます 行をタップすると その型の値のリストが表示されます これでも問題ありませんが さらに機能を改良することができます
AppEnumでは 各列挙ケースに タイトルしか必要ありませんが アイコンなどの追加情報を使用して 構成することもできます
これを行うには DisplayRepresentation イニシャライザを使用する必要があります 次に 各ケースに記号を追加します 再度実行すると ショートカットのピッカーに 画像が表示されるようになります インテントはパラメータサマリと呼ばれる 流暢な文のような表現を 使って構成できます パラメータサマリでは アクションとそのパラメータが 判読可能な方法で記述されます
パラメータが文字列に挿入された サマリを作ってみましょう ショートカットに選択可能なパラメータが 行に埋め込まれたサマリが表示され もっと便利な アクションの説明となっています
実際のところ これではまだ文章とはいえません パラメータにカスタムタイトルを追加して これを修正できます
値をリクエストする時に表示する カスタムダイアログも追加しておきましょう 今年から必要なすべての パラメータが含まれたパラメータサマリを インテントに実装すると ユーザーがMac上のSpotlightから アクションを実行できるようになりました Spotlightに追加された 新機能について詳しくは こちらのセッションをご覧ください
アプリのアクションを インテントとしてモデル化することで ユーザーが強力なショートカットや 自動化機能を構築できるようにします ただし インテントが アプリの核となるものである場合 アプリをインストールするとすぐ それらを利用できるようにする必要があります これらはアプリショートカットを 採用することで提供できます
アプリショートカットはApp Intentを 自動的にシステム全体に公開する型です アプリショートカットは Spotlightで 検索する時に特に便利です アプリショートカットはトリガフレーズを使って Siriに実行させることもできますし アクションボタンやApple Pencilの スクイーズでも実行することができます アプリショートカットはユーザー設定なしで ショートカットアプリに表示されます そして一番良いところは 数行のコードでビルドできるところです その方法を見てみましょう
アプリではAppShortcutsProviderを使って アプリショートカットを提供します アプリですべてのアプリショートカットを含む 1つのプロバイダを定義する必要があります アプリショートカットは インテントのインスタンスを受け取ることができ フレーズのリスト タイトル 画像を取ることもできます フレーズをSiriに発声または入力して アプリショートカットを実行できます 各フレーズにはapplicationName プレースホルダを含める必要があります フレーズには 最大1つの インテントパラメータを含めることができます 指定した場合は その型の各値に対して アプリショートカットが作成されます
アプリショートカットの作成に必要なのは このシンプルな構造体だけです ショートカットの新しいセクションに アプリのアプリショートカットが表示されます フレーズは作成されるアプリショートカットに 影響を与えます パラメータなしでフレーズを指定すると タイトルと画像名を使用して アプリショートカットが作成されます 私はAppEnumを使用して フレーズを指定したため 各ケースに対して アプリショートカットが作成されます
これでSiriやSpotlightで インテントを実行できるようになりました アプリショートカットはインテントを 見つけてもらいやすくする優れた方法です アプリショートカットの構築方法の詳細は WWDC23のこちらのセッションをご覧ください
観光名所はアプリの主要なコンセプトです インテントからそれらに誘導できたら 最高です ナビゲーションオプションは一定でしたが 観光名所は動的であるため AppEnumを使用できません 代わりに AppEntityを作成して 観光名所をモデル化します
アプリにはすでに観光名所の型があります その型をAppEntityに 適合させることもできますが ここでは Landmark Entity構造体を 新たに作成してみます この型は App Intentと基になっている データモデルの間の橋渡しとして機能します
AppEntityは識別が必要になるため IDを追加します このIDが永続的であることが 重要になります このIDを使用してエンティティの インスタンスを検索することができます これについては後で説明します インテントにはパラメータを指定できましたが エンティティには@Property属性で示された プロパティを指定できます これらはショートカット内で ユーザーに公開され 検索アクションや フィルターアクションで使用できます これらの値をデータモデルから 設定することもできますが 今年からエンティティプロパティに ゲッタを追加できるようになりました 新しい@ComputedProperty属性を 使用します これらの型の間で値をコピーしなくても データモデルに従うことができます AppEnumと同様に App Entityでも 型の表現と 型のインスタンスが必要になります
App Entityでは クエリと呼ばれる もう1つの情報が必要になります
値がわかっているAppEnumとは異なり App Entityは動的です 観光名所はアプリにいくつでも追加できます システムはクエリを使用することで エンティティを推測できます これはいくつかの異なる質問に 答えることによって行われます 最初の質問は 「どのようなエンティティがありますか」です クエリの仕事は その質問に回答し 一致するエンティティの コレクションを返すことです
クエリでは このような多くの質問が サポートされています 例えば Entity String Queryは 「この文字列に一致する エンティティはありますか」 とたずねます またEntity Property Queryは 「この州のすべての観光名所は 何ですか」とたずねます
すべてのクエリは非常に具体的な 1つの質問に答える必要があります 「このIDのエンティティは何ですか」 このように質問することで システムはエンティティを一意に参照でき 必要な時だけ解決することができます Entity Queryプロトコルに準拠した 型を作成することで クエリを提供できます entities(for:)メソッドは 「このIDのエンティティは何ですか」 という質問に クエリがどのように答えるかを示しています IDの配列を受け取り エンティティインスタンスの配列を返します 「どのようなエンティティがありますか」 という質問については後で説明します 多くの場合 クエリはローカルデータベースや その他の依存関係にアクセスして インスタンスをフェッチする必要があります @Dependency属性を使用して 依存関係をクエリに挿入できます 共有のApp Dependency Managerで 依存関係を登録する必要があります 依存関係はアプリのライフサイクルの できるだけ早い段階で登録します Landmark AppEntityを 作成することができました これをインテントで使用できます 旅行している時に最寄りの観光名所が 何であるかわかれば最高です それを教えてくれる App Intentを作成します 最初に 基本的な ClosestLandmarkIntentから始めましょう 最寄りの観光名所をデータモデルから フェッチする必要があります 依存関係はインテントでも使用できるため 依存関係を追加します performメソッドに Landmark Entityの ReturnsValueを追加します インテントパラメータとして使用する型は インテントから返すこともできます 戻り値を複数のステップから成る ショートカットなどの別のインテントの 入力として使用することができます ここではダイアログと ビュースニペットも返すようにします これによりインテントがインテントの結果を 表示したり話したりできるようになります 最後に performメソッドを実装します 最寄りの観光名所を見つけたら エンティティやダイアログ またビューを含む 結果が返されるようにします ダイアログとビューの両方を提供することで インテントを呼び出す方法に関わらず ユーザーがいつでも最寄りの観光名所を 確実に見つけられるようにします このインテントに アプリショートカットを作成しておけば このインテントをもっと簡単に実行できます これでユーザーは携帯をポケットに 入れたままでも SiriやSpotlightで インテントに簡単にアクセスできます インテントやエンティティ またクエリは App Intentの構成要素です 各プロトコルの下には さらにプロトコルや設定があります それを使用することで 追加の機能を提供できます 先ほどのAppEntityを さらに改良して追加の機能を提供する方法を 見てみましょう 最寄りの観光名所の写真を 簡単に表示できるようにしたいと思います そのためにショートカットアプリを 使用できます まず ショートカットを作成して Find Closest Landmarkインテントを追加し 結果を確認するために Show Contentアクションを追加します このアクションはインテントの 結果を受け取り それをレンダリングします このアクションは デフォルトではLandmark Entityの表現を 表示しますが 任意のエンティティプロパティを選択して レンダリングすることもできます
Landmark Entityは 画像を直接保持しているわけではなく 画像までのパスを保持しています Transferableプロトコルを使用して エンティティに対して 画像表現を宣言できます Transferableは 型に対して 様々なデータ表現を記述するための 宣言型プロトコルです これらの表現を使用して アプリ間でデータを共有できます 型のTransferRepresentationの一部として 画像データを提供します ショートカットに戻りましょう エンティティの画像表現を 表示できるようになりました ショートカットを実行すると 観光名所の画像が表示されます 画像表現を宣言することには さらにメリットがあります 写真を受け取るアクションの入力として この画像の値を使用できます 他のアプリのアクションでも構いません Transferableの詳細については こちらのセッションをご覧ください
システム内でエンティティを 見つけやすくするために もう1箇所エンティティを 変更したいと思います Spotlightには アプリ間で実行される 強力なセマンティック検索が搭載されています エンティティをSpotlightに提供することで システムによるコンテンツの理解を向上できます エンティティをインデックス化するには IndexedEntityプロトコルを採用します IndexedEntityはCore Spotlight 属性セットを含むAppEntityです
今年からSpotlightのインデックスキーを プロパティに直接追加できるようになりました プロパティに注釈を付けることでSpotlightに より関連性の高い情報を表示できます インデックス化したエンティティを 提供すると フレームワークによって 検索可能な項目と 属性セットが作成されます エンティティを提供したら それらをSpotlightで検索できます デフォルトではエンティティをタップすると アプリがフォアグラウンドで実行されます この機能をさらに改良して 観光名所の詳細ビューが直接開くようにします まず OpenIntentプロトコルに準拠した インテントを作成します このプロトコルを採用するインテントは 実行する前に 自動的にアプリを開きます そのため 対応モードを追加する手順は スキップします OpenIntentには targetパラメータが必要です Spotlightで エンティティをタップすると 一致するOpenIntentが存在する場合は それが実行されます
ナビゲータを呼び出す代わりに ナビゲーション専用に設計された新しい TargetContentProvidingIntentプロトコルを 採用できるようになりました これらのインテントには performメソッドは必要ありません 代わりにonAppIntentExecution修飾子を ビューにアタッチします クロージャ内では インテントのパラメータを使用して SwiftUIナビゲーションを実行します
これで Spotlightから 観光名所をタップすると 観光名所の詳細ビューに 直接移動できるようになりました このセクションのまとめとして クエリとそれがどのようにエンティティを 提供するかについて少し説明します これを実際に確認するために観光名所を開く 新しいアプリショートカットを作成します 新しいアプリショートカットを2つの フレーズとともにプロバイダに追加しました 一方には観光名所パラメータを含め もう一方には含めませんでした ところが アプリショートカットを実行しても 選択する観光名所が表示されません お察しの通り 観光名所を表示するには クエリを使用する必要があります オプションのsuggestedEntitiesメソッドを EntityQueryに実装して ユーザーのお気に入りの 観光名所を返します インテントを再度実行すると 提案されたエンティティの リストが表示されます 提案されたエンティティには 別の使い方もあります このインテントにパラメータ化したフレーズを 追加したことを思い出してください updateAppShortcutParametersメソッドを プロバイダで呼び出すことによって 提案された各エンティティに対して アプリショートカットを生成できます これでSiriとショートカットを使って お気に入りの観光名所に 直接かつ簡単に移動できるようになりました クエリはエンティティに関する その他の多くの質問に答えることができます エンティティすべてが メモリに収まる場合は EnumerableEntityQueryを使用して それらすべてを返すことができます そこからApp Intentがより複雑なクエリを 導き出すことができるようになります EntityPropertyQueryでは 一連のプレディケートに従ってソートされた エンティティのリストを返すことができます ここでは EntityStringQueryを実装して文字列からの エンティティの検索をサポートします 名前または説明が文字列と一致する 観光名所のリストが返されるようにします これにより観光名所が必要になる インテントを設定する際に すべての観光名所を 検索できるようになります 今日はApp Intentフレームワークと その多くの機能のさわりの部分を 紹介したにすぎません App Intentのドキュメントをご覧になり フレームワークを使用してユーザーを喜ばせる その他の様々な方法をご確認ください 最後に App Intentを駆動している アーキテクチャに関する情報を提供します App Intentでアプリを構築する場合 コードが唯一の信頼できる情報源となります App Intentに設定ファイルや 構成ファイルは必要ありません Swiftソースコードは ビルド時に読み取られ App Intent表現が 生成されます この表現はアプリまたは フレームワーク内に保存されます アプリがインストールされると システムはこのデータを使用して アプリの機能を理解します その際にアプリを実行する必要はありません これがどのように機能するか 1つのインテントを見てみましょう インテントの名前は アクションの一意のIDになります インテントのタイトルは ユーザーがインテントを区別するのに役立ちます performメソッドのリターン署名には インテントの結果を レンダリングする方法が説明されています このプロセスは実行時ではなく ビルド時に行われるため 特定の値には 定数値を指定する必要があります 例えば インテントのタイトルは 一定である必要があります 関数や計算型プロパティを呼び出すと エラーになります この処理はアプリのターゲットごとに 個別に行われます ターゲット間でApp Intentの型を 適切に共有するために 注意が必要なことが いくつかあります 昨年はアプリやApp Intentの拡張機能に フレームワーク内で定義された App Intentの型を参照する機能が 追加されましたが 今年からApp Intentを Swiftパッケージやスタティックライブラリに 追加できるようになったことを お知らせできることをうれしく思います ターゲット間でApp Intentの型を 使用する場合 各ターゲットに関する追加情報を ランタイムに提供する必要があります これにより型が適切にインデックス化され 検証されるようになります これがどのように行われるのか 詳しく見てみましょう このアプリにはすべてのApp Intentコードを 含む1つのターゲットしかありません 新しいApp Intent拡張機能を追加して いくつかのインテントをホストしましょう いずれのターゲットも 観光名所へのアクセスが必要になります そのため Swiftパッケージを作成し それにLandmark Entityを移動します ターゲット間で型を共有するには App Intentパッケージとして 各ターゲットを登録する必要があります まず エンティティと同じターゲットに App Intentパッケージを作成します 別のApp Intentパッケージを アプリターゲットに追加します 含まれているパッケージの リストを提供できます 先ほど作成したものを含めます 最後に拡張機能から 同じことを行います これにより App Intentランタイムが パッケージで定義されたすべての型に 適切にアクセスできるようにします スタティックライブラリに コンパイルされていないコードを参照する場合は App Intentパッケージを 使用する必要があります
今日の話の締めくくりとして 今回App Intentを 初めて見たという方は 最初のアプリショートカットを アプリに追加するなど簡単なことから始めて そこからフレームワークについて 詳しく調べ ユーザーに最大のメリットをもたらす機能は 何かを確認してください 今年は App Intentについて 他にも素晴らしいセッションがあります ご視聴ありがとうございました
-
-
3:23 - Navigate Intent
struct NavigateIntent: AppIntent { static let title: LocalizedStringResource = "Navigate to Landmarks" static let supportedModes: IntentModes = .foreground @MainActor func perform() async throws -> some IntentResult { Navigator.shared.navigate(to: .landmarks) return .result() } }
-
5:02 - Navigation Option App Enum
enum NavigationOption: String, AppEnum { case landmarks case map case collections static let typeDisplayRepresentation: TypeDisplayRepresentation = "Navigation Option" static let caseDisplayRepresentations: [NavigationOption: DisplayRepresentation] = [ .landmarks: "Landmarks", .map: "Map", .collections: "Collections" ] }
-
5:38 - Navigate Intent with Parameter
struct NavigateIntent: AppIntent { static let title: LocalizedStringResource = "Navigate to Section" static let supportedModes: IntentModes = .foreground @Parameter var navigationOption: NavigationOption @MainActor func perform() async throws -> some IntentResult { Navigator.shared.navigate(to: navigationOption) return .result() } }
-
6:57 - Case Display Representations with Images
static let caseDisplayRepresentations = [ NavigationOption.landmarks: DisplayRepresentation( title: "Landmarks", image: .init(systemName: "building.columns") ), NavigationOption.map: DisplayRepresentation( title: "Map", image: .init(systemName: "map") ), NavigationOption.collections: DisplayRepresentation( title: "Collections", image: .init(systemName: "book.closed") ) ]
-
7:28 - Navigation Option With Parameter Summary
struct NavigateIntent: AppIntent { static let title: LocalizedStringResource = "Navigate to Section" static let supportedModes: IntentModes = .foreground static var parameterSummary: some ParameterSummary { Summary("Navigate to \(\.$navigationOption)") } @Parameter( title: "Section", requestValueDialog: "Which section?" ) var navigationOption: NavigationOption @MainActor func perform() async throws -> some IntentResult { Navigator.shared.navigate(to: navigationOption) return .result() } }
-
9:22 - App Shortcuts Provider and Navigation Intent App Shortcut
struct TravelTrackingAppShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: NavigateIntent(), phrases: [ "Navigate in \(.applicationName)", "Navigate to \(\.$navigationOption) in \(.applicationName)"a ], shortTitle: "Navigate", systemImageName: "arrowshape.forward" ) } }
-
11:02 - Landmark Entity
struct LandmarkEntity: AppEntity { var id: Int { landmark.id } @ComputedProperty var name: String { landmark.name } @ComputedProperty var description: String { landmark.description } let landmark: Landmark static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Landmark") var displayRepresentation: DisplayRepresentation { DisplayRepresentation(title: "\(name)") } static let defaultQuery = LandmarkEntityQuery() }
-
13:19 - Landmark Entity Query
struct LandmarkEntityQuery: EntityQuery { @Dependency var modelData: ModelData func entities(for identifiers: [LandmarkEntity.ID]) async throws -> [LandmarkEntity] { modelData .landmarks(for: identifiers) .map(LandmarkEntity.init) } }
-
13:50 - App Dependency Manager
@main struct LandmarksApp: App { init() { AppDependencyManager.shared.add { ModelData() } } }
-
14:18 - Closest Landmark Intent
struct ClosestLandmarkIntent: AppIntent { static let title: LocalizedStringResource = "Find Closest Landmark" @Dependency var modelData: ModelData @MainActor func perform() async throws -> some ReturnsValue<LandmarkEntity> & ProvidesDialog & ShowsSnippetView { let landmark = try await modelData.findClosestLandmark() return .result( value: landmark, dialog: "The closest landmark to you is \(landmark.name)", view: ClosestLandmarkView(landmark: landmark) ) } }
-
15:18 - Closest Landmark App Shortcut
AppShortcut( intent: ClosestLandmarkIntent(), phrases: [ "Find closest landmark in \(.applicationName)" ], shortTitle: "Closest landmark", systemImageName: "location" )
-
16:33 - Transferable
extension LandmarkEntity: Transferable { static var transferRepresentation: some TransferRepresentation { DataRepresentation(exportedContentType: .image) { return try $0.imageRepresentationData } } }
-
17:31 - Indexed Entity
struct LandmarkEntity: IndexedEntity { // ... @Property( indexingKey: \.displayName ) var name: String @Property( indexingKey: \.contentDescription ) var description: String }
-
18:17 - Open Landmark Intent
struct OpenLandmarkIntent: OpenIntent, TargetContentProvidingIntent { static let title: LocalizedStringResource = "Open Landmark" @Parameter(title: "Landmark", requestValueDialog: "Which landmark?") var target: LandmarkEntity } struct LandmarksNavigationStack: View { @State var path: [Landmark] = [] var body: some View { NavigationStack(path: $path) {} .onAppIntentExecution(OpenLandmarkIntent.self) { intent in path.append(intent.target.landmark) } } }
-
19:24 - Open Landmark App Shortcut
AppShortcut( intent: OpenLandmarkIntent(), phrases: [ "Open \(\.$target) in \(.applicationName)", "Open landmark in \(.applicationName)" ], shortTitle: "Open", systemImageName: "building.columns" )
-
19:39 - Suggested Entities
struct LandmarkEntityQuery: EntityQuery { // ... func suggestedEntities() async throws -> [LandmarkEntity] { modelData .favoriteLandmarks() .map(LandmarkEntity.init) } }
-
20:06 - Update App Shortcut Parameters
TravelTrackingAppShortcuts.updateAppShortcutParameters()
-
20:25 - EnumerableEntityQuery
extension LandmarkEntityQuery: EnumerableEntityQuery { func allEntities() async throws -> [LandmarkEntity] { // ... } }
-
20:36 - EntityPropertyQuery
extension LandmarkEntityQuery: EntityPropertyQuery { static var properties = QueryProperties { // ... } static var sortingOptions = SortingOptions { // ... } func entities( matching comparators: [Predicate<LandmarkEntity>], mode: ComparatorMode, sortedBy: [Sort<LandmarkEntity>], limit: Int? ) async throws -> [LandmarkEntity] { // ... } }
-
20:44 - EntityStringQuery
extension LandmarkEntityQuery: EntityStringQuery { func entities(matching: String) async throws -> [LandmarkEntity] { modelData .landmarks .filter { $0.name.contains(matching) || $0.description.contains(matching) } .map(LandmarkEntity.init) } }
-
23:10 - App Intents Package
// TravelTrackingKit public struct TravelTrackingKitPackage: AppIntentsPackage {} public structaLandmarkEntity: AppEntity {} // TravelTracking struct TravelTrackingPackage: AppIntentsPackage { static var includedPackages: [any AppIntentsPackage.Type] { [TravelTrackingKitPackage.self] } } struct OpenLandmarkIntent: OpenIntent {} // TravelTrackingAppIntentsExtension struct TravelTrackingExtensionPackage: AppIntentsPackage { static var includedPackages: [any AppIntentsPackage.Type] { [TravelTrackingKitPackage.self] } } struct FavoriteLandmarkIntent: AppIntent {}
-