-
Discover new capabilities in the App Intents framework
Level up your App Intents adoption with advanced features to make it faster, more flexible, and more relevant. Find out how ValueRepresentation and RelevantEntities make your content more discoverable and allow it to travel across apps, EntityCollection improves performance, and SyncableEntity let you scale across devices. Explore richer parameter types including union values and long-running intents that handle cancellation gracefully.
Chapters
- 0:00 - Introduction
- 2:40 - Share entities across apps with ValueRepresentation
- 3:45 - Register relevant entities with RelevantEntities
- 7:05 - Handle entities efficiently with EntityCollection
- 8:55 - Use entities across devices with SyncableEntity
- 11:01 - Richer parameter types
- 12:38 - Union value parameters
- 13:26 - Extend execution with LongRunningIntent
- 15:27 - Target the right process with ExecutionTargets
- 17:14 - Next steps
Resources
-
Search this video…
-
-
0:01 - Share structured entities with ValueRepresentation
struct LandmarkEntity: AppEntity, Transferable { var id: Int var landmark: Landmark // contains CLLocationCoordinate2D static var transferRepresentation: some TransferRepresentation { ValueRepresentation( exporting: { entity in PlaceDescriptor( representations: [.coordinate(entity.landmark.locationCoordinate)], commonName: entity.landmark.name ) } ) } } // If the entity already has a PlaceDescriptor property, use a key-path — much less code: struct LandmarkEntity: AppEntity, Transferable { var id: Int @Property var placeDescriptor: PlaceDescriptor static var transferRepresentation: some TransferRepresentation { ValueRepresentation(exporting: \.placeDescriptor) } } -
5:18 - Register relevant entities with RelevantEntities
// Suggest playlists for the workout session let playlistEntities = [dailyRun, runningMix] let workoutContext = AppEntityContext.audio(.workout(activityType: .running)) try await RelevantEntities.shared.updateEntities( playlistEntities, for: workoutContext ) // Clear all entities for a context try await RelevantEntities.shared.removeAllEntities(for: workoutContext) // Remove specific entities from a context try await RelevantEntities.shared.removeEntities(playlistEntities, from: workoutContext) // Or remove all entities across all contexts try await RelevantEntities.shared.removeAllEntities() -
7:15 - Handle large entity sets with EntityCollection
struct TagPhotosIntent: AppIntent { static let title: LocalizedStringResource = "Tag Travel Photos" @Parameter var photos: EntityCollection<PhotoEntity> // was: [PhotoEntity] @Parameter var tag: String func perform() async throws -> some IntentResult { modelData.tagPhotos(ids: photos.identifiers, tag: tag) // was: tagPhotos(photos, tag: tag) return .result() } } -
10:14 - Make entity IDs stable with SyncableEntity
// If your ID is already stable across devices (server UUID, CloudKit record ID): struct PhotoEntity: AppEntity, SyncableEntity { var id: Int // Already stable across devices — that's it } // If you use local IDs, pair a local and a stable ID: struct PhotoEntity: AppEntity, SyncableEntity { var id: SyncableEntityIdentifier<String, String> init(localID: String, stableID: String) { self.id = SyncableEntityIdentifier(local: localID, stable: stableID) } } -
11:58 - Accept multiple types with @UnionValue
@UnionValue enum TravelGalleryContent { case landmarkCollection(LandmarkCollectionEntity) case photoAlbum(PhotoAlbumEntity) static let typeDisplayRepresentation: TypeDisplayRepresentation = "Travel Gallery" static let caseDisplayRepresentations: [Cases: DisplayRepresentation] = [ .landmarkCollection: "Landmark Collection", .photoAlbum: "Photo Album" ] } -
13:41 - Run beyond 30 s with LongRunningIntent + CancellableIntent
struct UploadPhotoIntent: LongRunningIntent, CancellableIntent { static let title: LocalizedStringResource = "Upload Photo" @Parameter var photo: IntentFile func perform() async throws -> some IntentResult & ProvidesDialog { let result = try await performBackgroundTask { let chunks = calculateChunks(for: photo) progress.totalUnitCount = Int64(chunks) for chunk in 1...chunks { try Task.checkCancellation() try await uploadChunk(chunk) progress.completedUnitCount = Int64(chunk) } return "Upload complete!" } onCancel: { reason in cleanup(for: reason) } return .result(dialog: "\(result)") } } -
16:54 - Control which process runs your intent with ExecutionTargets
// Write operation — needs the main app struct UpdateFavoriteIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { .main } } // Standalone download — runs in the extension struct DownloadPhotoIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { .appIntentsExtension } } // Display-only — runs in the widget extension struct GetLandmarkStatusIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { .widgetKitExtension } } // Works in either — lets the system choose struct TagPhotosIntent: AppIntent { static var allowedExecutionTargets: ExecutionTargets { [.main, .appIntentsExtension] } }
-
-
- 0:00 - Introduction
The 2027 App Intents updates — more control, flexibility, and a smoother developer experience across Siri, Shortcuts, Spotlight, Widgets, and Apple Intelligence. Three areas: entity enhancements, richer parameters, and intent execution, built on the Landmarks Travel Tracking sample.
- 2:40 - Share entities across apps with ValueRepresentation
Beyond Transferable's File and Data representations, the new ValueRepresentation shares structured types the system understands, for example exporting a landmark as a PlaceDescriptor (GeoToolbox) so it flows to Maps for directions. Use a key-path if the entity already has the property.
- 3:45 - Register relevant entities with RelevantEntities
Spotlight indexing and interaction donation can't surface never-seen, never-used content. RelevantEntities lets you suggest entities with a context (such as running playlists when a workout starts) via updateEntities, and remove them by context, by entity, or entirely.
- 7:05 - Handle entities efficiently with EntityCollection
Resolving every entity before an intent runs is costly at scale (tagging thousands of photos). EntityCollection passes just identifiers to perform() without full resolution, a one-line parameter-type change that made tagging 1000 photos nearly instant.
- 8:55 - Use entities across devices with SyncableEntity
Siri conversations now continue across devices, but local IDs differ per device. SyncableEntity declares a stable ID (server UUID or CloudKit record ID); when you only have local IDs, SyncableEntityIdentifier pairs a local and a stable ID so on-device code uses local and the system uses stable.
- 11:01 - Richer parameter types
Declaring a @Parameter gives a native picker, Siri understanding, and localization for free, now extended to more native types like Duration (no custom time pickers) and PersonNameComponents, working across Siri, Shortcuts, and Widgets.
- 12:38 - Union value parameters
A @UnionValue enum lets one parameter accept multiple types, for example a single widget showing photos from either a landmark collection or a photo album. The macro generates type info, case metadata, and picker support (typeDisplayRepresentation, caseDisplayRepresentations), and works everywhere including Shortcuts.
- 13:26 - Extend execution with LongRunningIntent
Intents normally have 30 seconds; LongRunningIntent runs beyond it, manages the background task lifecycle, and shows progress as a Live Activity. Wrap work in performBackgroundTask and report progress (it builds on ProgressReportingIntent). Add CancellableIntent's onCancel to clean up gracefully; it also supports background GPU access.
- 15:27 - Target the right process with ExecutionTargets
When intents live in a shared package linked by the app and extensions, the system picks a process by heuristics, not always right (for example a widget favorite button needs the writing main app). ExecutionTargets overrides this to target the main app, an App Intents extension, a WidgetKit extension, or any combination.
- 17:14 - Next steps
Add ValueRepresentation to carry structured data, register relevant content, adopt EntityCollection for large entity sets, and add LongRunningIntent for work over 30 seconds. See "Code-along: Make your app available to Siri" and "Validate your App Intents adoption with AppIntentsTesting."