ハイライト
App Intents は 2024 年に 4 つの主要な更新を導入しました。IndexedEntity でエンティティを Spotlight にインデックス、Transferable で標準フォーマットに変換、URLRepresentable で Intent タイプを Universal Link にマッピング、UnionValue などの開発者体験の改善です。
主要内容
以前、app のコンテンツは CSSearchableItem として Spotlight に表示されるだけでした。検索能力が制限され、ユーザーがコンテンツを見つけてもシステムは追加操作ができませんでした。Siri も app 内部のエンティティ理解が限定的で、キーワードマッチングに依存し、コンテンツの意味を真に理解できませんでした。
2024 年、Apple は IndexedEntity プロトコルを導入しました。App Entity を CSSearchableItem 経由なしで Spotlight に直接ドネートできます。さらに重要なのは、Siri と Apple Intelligence が app コンテンツを理解する道が開けたことです。Spotlight でトレイルを検索すると、Siri は文字列マッチだけでなく「Trail」エンティティとして理解し、属性と実行可能な操作を把握します。
もう一つの課題は app 間共有でした。メールや SNS へ app コンテンツを共有する際、クリップボードかカスタム共有拡張しかありませんでした。Entity を Transferable に準拠させると、PDF、画像、リッチテキストなど標準フォーマットに直接変換でき、それらを受け入れる任意の app で受け取れます。
Universal Links 統合はディープリンク問題を解決します。以前は Intent タイプを URL に直接マッピングできず、Shortcuts や Siri が app の特定ページへジャンプしにくい状態でした。URLRepresentableIntent により Intent タイプが Universal Link を自動生成し、既存の URL 処理コードを再利用できます。
詳細
IndexedEntity:エンティティを Spotlight にインデックス
IndexedEntity は App Entity を CSSearchableIndex で直接インデックスできる新プロトコルです(02:02)。最も基本的な実装はプロトコル準拠と app 起動時の indexAppEntities 呼び出しだけです:
// TrailEntity は IndexedEntity プロトコルに準拠
struct TrailEntity: IndexedEntity {
// デフォルト実装は DisplayRepresentation で属性セットを埋める
}
// App の init メソッドですべてのエンティティをインデックス
class AppDelegate {
init() {
let trails = DataManager.shared.trails
CSSearchableIndex.default().indexAppEntities(trails)
}
}
キーポイント:
IndexedEntityプロトコルはDisplayRepresentationを CSSearchableItem の属性セットに自動変換indexAppEntitiesはAppEntity配列を受け取り、複数エンティティを一括インデックス- インデックス後、エンティティは Spotlight 検索結果に表示され、Intent と関連付け可能
デフォルト実装は DisplayRepresentation のみ使用。attributeSet をカスタマイズして位置情報やキーワードなど追加データを提供できます(02:47):
extension TrailEntity {
var attributeSet: CSSearchableItemAttributeSet {
let attributeSet = CSSearchableItemAttributeSet(contentType: .UTTypeText)
// 位置情報を追加
attributeSet.city = location.city
attributeSet.state = location.state
// 検索を助けるキーワードを追加
attributeSet.keywords = activities.map { $0.rawValue }
return attributeSet
}
}
キーポイント:
attributeSet計算プロパティでインデックスメタデータをカスタマイズ- 位置情報(
city、state)でローカル検索の関連性を向上 keywordsフィールドで同義語や関連語を追加し、ユーザーがコンテンツを見つけやすく
既に CSSearchableItem でインデックスしている場合、新しい associateAppEntity メソッドで既存インデックスと App Entity を関連付け(04:12)、インデックスロジックの再実装は不要です。
Transferable:エンティティを app 間で共有
Transferable はモデルのシリアライズ/デシリアライズを記述する宣言的プロトコルです。App Entity も Transferable に準拠できます(05:05):
// Activity Summary Entity は Transferable プロトコルに準拠
struct ActivitySummaryEntity: AppEntity, Transferable {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "アクティビティ概要")
static var defaultQuery = ActivitySummaryQuery()
var id: String
var date: Date
var caloriesBurned: Int
var distance: Double
// 転送可能な表現形式を定義
static var transferRepresentation: some TransferRepresentation {
// 1. 最高の忠実度:プライベートな Codable 表現
DataRepresentation(contentType: .applicationJSON) { summary in
try JSONEncoder().encode(summary)
} importing: { data in
try JSONDecoder().decode(ActivitySummaryEntity.self, from: data)
}
// 2. リッチテキスト形式
DataRepresentation(contentType: .rtf) { summary in
summary.toRichText()
}
// 3. 画像形式
FileRepresentation(contentType: .png) { summary in
let image = summary.generateChartImage()
return SavedFileRepresentable(image)
}
// 4. プレーンテキスト形式(最低の忠実度)
DataRepresentation(contentType: .plainText) { summary in
summary.description.data(using: .utf8)!
}
}
}
キーポイント:
transferRepresentationはサポートする表現形式リストを返す静的計算プロパティ- 順序が重要:最高から最低の忠実度で並べる
DataRepresentationはメモリ内データ(JSON、RTF、テキスト)用FileRepresentationはファイルタイプ(PNG、PDF)用SavedFileRepresentableは生成した一時ファイルをラップし、他 app からアクセス可能に
Shortcuts では、Entity が異なるパラメータタイプを受け入れる Action に渡されると、App Intents が最適な表現形式を自動選択して変換します(07:02)。
Transferable と AppEntity の組み合わせには現時点で制限があります(08:00):Xcode はコンパイル時に Transferable 表現を理解する必要があり、ProxyRepresentation は @Property 属性付きエンティティプロパティのみ参照可能です。
IntentFile:変換可能なコンテンツを受け取る
Intent が IntentFile パラメータを受け入れる場合、サポートするコンテンツタイプを宣言できます(08:49):
struct AppendToNoteIntent: AppIntent {
static var description = "内容をメモに追加"
@Parameter(title: "添付ファイル")
var attachment: IntentFile
@Parameter(title: "メモ")
var note: NoteEntity
static var parameterSummary = some ParameterSummary {
Summary("\(\.$attachment) を \(\.$note) に追加")
}
func perform() async throws -> some IntentResult & ReturnsValue<NoteEntity> {
// ファイルタイプを確認
if attachment.contentType == .plainText {
let textContent = try await attachment.textContent
note.append(text: textContent)
} else if attachment.contentType == .png {
let imageURL = try await attachment.fileURL
note.append(imageFrom: imageURL)
}
return .result(value: note)
}
}
キーポイント:
IntentFileパラメータは Transferable に準拠する任意の Entity を受け入れ可能contentTypeプロパティで実際のコンテンツタイプを確認textContentとfileURLはコンテンツ抽出用の新 API- Siri と Shortcuts が Entity を IntentFile サポートタイプに自動変換
FileEntity:ファイルをエンティティとして
ドキュメントベース app やファイル管理 app では、FileEntity でファイル自体を Entity にできます(10:18):
struct PhotoEntity: AppEntity, FileEntity {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "写真")
static var defaultQuery = PhotoQuery()
var id: FileEntityIdentifier
var name: String
// FileEntity が要求するプロパティ
static var supportedContentTypes: [UTType] {
[.image, .png, .jpeg]
}
// ファイル URL を識別子として使用
init(url: URL) {
self.id = FileEntityIdentifier(url: url)
self.name = url.lastPathComponent
}
}
キーポイント:
FileEntityはファイル自体を表す特殊なAppEntitysupportedContentTypesでサポートするファイルタイプを宣言FileEntityIdentifierは URL ブックマークデータを使用し、ファイル移動・リネーム後も有効- 他 app は IntentFile 経由でこれらのファイルに安全にアクセス
URLRepresentable:ディープリンク
URLRepresentableEntity と URLRepresentableIntent で Entity と Intent を Universal Link として表現できます(12:06):
// Entity がリンク表現をサポート
extension TrailEntity: URLRepresentableEntity {
static var urlRepresentation: URLRepresentation {
URLRepresentation(string: "https://trails.example.com/trail/\(Self.self.\.id)")
}
}
// Intent がリンク表現をサポート
struct OpenTrailIntent: AppIntent, OpenIntent, URLRepresentableIntent {
static var description = "トレイル詳細を開く"
static var openUrlWhenRun: Bool = true
@Parameter(title: "トレイル")
var trail: TrailEntity
static var parameterSummary = some ParameterSummary {
Summary("\(\.$trail) を開く")
}
// perform の実装は不要。App Intents が URL 処理コードを呼び出す
}
キーポイント:
URLRepresentationは文字列補間を使用し、@Property付きプロパティを挿入可能URLRepresentableIntentとOpenIntentの組み合わせで URL オープンを自動処理openUrlWhenRunで実行時に URL を開くか制御perform実装不要 — フレームワークが既存 Universal Link 処理コードを再利用
別の方法として perform から OpenURLIntent を返すこともできます(14:24):
struct CreateTrailIntent: AppIntent {
func perform() async throws -> some IntentResult & OpensIntent {
// 新しいトレイルを作成
let newTrail = try await createTrail()
// OpenURLIntent を返して新しく作成したトレイルを開く
return .result(opensIntent: OpenURLIntent(newTrail))
}
}
UnionValue:和型パラメータ
新しい UnionValue マクロで、パラメータが複数タイプのいずれかを受け入れられます(14:38):
enum DayPassType: UnionValue {
case park(ParkEntity)
case trail(TrailEntity)
}
struct BuyDayPassIntent: AppIntent {
static var description = "1日券を購入"
@Parameter(title: "チケット種別")
var passType: DayPassType
func perform() async throws -> some IntentResult {
switch passType {
case .park(let park):
try await purchaseParkPass(for: park)
case .trail(let trail):
try await purchaseTrailPass(for: trail)
}
return .result()
}
}
キーポイント:
UnionValueマクロは enum を和値タイプとしてマーク- 各 case は関連値を 1 つだけ持つ必要がある
- すべての関連値タイプは互いに異なる必要がある
- 「または」パラメータと考える:ParkEntity または TrailEntity
開発者体験の改善
Xcode 16.0 では AppEntity プロパティや AppIntent パラメータの title が必須ではなくなりました(16:02):
// Xcode 16 より前:title の指定が必須
@Parameter(title: "トレイルコレクション")
var trailCollection: TrailCollection
// Xcode 16:title は省略可能
@Parameter
var trailCollection: TrailCollection // title として "Trail Collection" を自動生成
Xcode はプロパティ名から title を自動生成します。カスタムタイトル(「TrailCollection」ではなく「Featured Collection」など)が必要な場合は手動指定可能です。
Framework サポートも改善されました(17:05):Framework 内で AppEntity を定義し、App や Extension から参照できます。以前はすべてのタイプが同一モジュール内である必要がありました。
重要ポイント
-
コアコンテンツに IndexedEntity をすぐ追加
なぜ価値があるか:Apple Intelligence が app コンテンツを理解する最も直接的な道です。数行のコードでエンティティを Spotlight と Siri 提案に表示でき、ユーザーは app を開かずにコンテンツを発見・操作できます。
始め方:最もよく使う Entity(ドキュメント、プロジェクト、記事)から、
IndexedEntity準拠を追加し app 起動時にindexAppEntitiesを呼び出す。既存のCSSearchableItemコードがある場合はassociateAppEntityで接続。 -
共有可能なコンテンツに Transferable を実装
なぜ価値があるか:ユーザーは app コンテンツをメール、メモ、SNS へ共有することが多い。Transferable により標準フォーマット(PDF、画像、リッチテキスト)で任意の app が受け取れ、共有体験が大幅に向上します。
始め方:共有されそうなコンテンツ(レポート、サマリー、チャート)を特定し、対応 Entity に
Transferableサポートを追加。表現形式は忠実度順:私有フォーマット(同一 app 転送用)→ 汎用フォーマット(RTF、PNG、PDF)→ プレーンテキスト。 -
URLRepresentable でディープリンクを簡素化
なぜ価値があるか:Universal Links はディープリンクの標準。
URLRepresentableIntentにより Intent が正しい URL を自動生成し、既存ルーティングコードを再利用、ナビゲーションロジックの重複実装が不要です。始め方:ジャンプが必要な Entity に
URLRepresentableEntity、オープン系 Intent にURLRepresentableIntentを追加。URL テンプレートに Entity ID など一意識別子を含める。 -
UnionValue でパラメータタイプを簡素化
なぜ価値があるか:異なるオブジェクトタイプで同じ操作(「日票購入」が公園票またはトレイル票)が必要な場合、UnionValue が型安全な「または」関係を提供し、2 つの別 Intent より明確です。
始め方:複数の類似タイプパラメータを受け入れる Intent を探し、
UnionValueを使う単一 Intent に統合。各 case の関連値タイプが互いに異なることを確認。
関連セッション
- Bring your app to Siri — SiriKit と App Intents で音声から app 機能にアクセスする方法
- Bring your app’s core features to users with App Intents — Intent、Entity、Query など App Intents の核心概念を深掘り
- Control your app from anywhere with Controls — App Intents でコントロールセンター コントロールを構築
- Bring your Live Activity to Apple Watch — Live Activity を Apple Watch の Smart Stack へ
- What’s new in SwiftUI — App Intents と深く統合された SwiftUI の年次アップデート
コメント
GitHub Issues · utterances