WWDC Quick Look đź’“ By SwiftGGTeam
Discover new capabilities in the App Intents framework

Discover new capabilities in the App Intents framework

Watch original video

Highlight

The App Intents framework adds capabilities such as ValueRepresentation, RelevantEntities, EntityCollection, SyncableEntity, @UnionValue, LongRunningIntent, CancellableIntent, and ExecutionTargets, making cross-app data sharing smoother, large entity handling more efficient, cross-device conversations more reliable, and long-running tasks easier to control.

Core Content

Starting from a real problem

You have a travel-tracking app, and users want to share the landmarks they have saved.

A user creates a Shortcuts automation: find nearby landmarks and send them to a friend with a message. Your landmark entity already implements Transferable, so Shortcuts can convert it into a format Mail can use. That part works.

Then the user changes the scenario and wants to send the landmark to a Maps app for navigation. Here is the problem: Maps needs structured data such as coordinates and addresses, not a file. Existing FileRepresentation and DataRepresentation types handle PDFs, images, and other types with clear file formats, but they cannot represent pure structured data such as coordinates.

This is what ValueRepresentation solves. It lets your entity carry system-known structured types so data can flow between apps.

A new relevance mechanism

Another scenario: you build a music app and carefully curate a high-BPM playlist for running.

When users set up a running workout in a fitness app, the system recommends playlists. How does your new playlist enter that list?

The traditional approaches are Spotlight indexing, which lets users search for content, and interaction donation, which tells the system how users use your app. But a new playlist has a problem: the user does not know it exists, has not searched for it, and has not played it, so there is no interaction to donate.

What you need is a way to tell the system, “This playlist is relevant in a running context.” That is RelevantEntities.

Solving a performance bottleneck

As app data grows, users might tag thousands of photos at once.

Your intent definition is simple: receive an array of photo entities and add a keyword to each one. But execution is slow, because before running the intent, the system resolves every entity and fetches every property. You only need IDs, but the system resolves full photo metadata.

EntityCollection fixes this by passing only entity IDs instead of resolving full entities.

Cross-device entity references

The user asks Siri on iPhone to add a photo to an album, then moves to another device and asks Siri to tag that same photo. Siri cannot find it.

The reason is that each device generates different local IDs. To continue conversations across devices, Siri needs a stable ID that is identical everywhere, such as a server UUID or CloudKit record ID.

SyncableEntity tells the system that an entity ID is stable across devices.

Other enhancements

Parameter types now support native types such as Duration and PersonNameComponents. A single parameter can also accept multiple types with @UnionValue. Intents can run longer than 30 seconds with LongRunningIntent, handle cancellation gracefully with CancellableIntent, and specify their execution process with ExecutionTargets.

Details

ValueRepresentation: Share structured data

([0:42](https://developer.apple.com/videos/play/wwdc2026/345/?time=42))

ValueRepresentation lets your entity carry system-understood structured types, such as PlaceDescriptor from the GeoToolbox framework.

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
                )
            }
        )
    }
}

Key points:

  • ValueRepresentation is a new kind of TransferRepresentation
  • The exporting closure returns a system-known type, PlaceDescriptor in this example
  • PlaceDescriptor contains coordinates and a name, so Maps can navigate directly

If the entity already has a PlaceDescriptor property, you can use a simpler key-path form:

struct LandmarkEntity: AppEntity, Transferable {
    var id: Int
    @Property var placeDescriptor: PlaceDescriptor

    static var transferRepresentation: some TransferRepresentation {
        ValueRepresentation(exporting: \.placeDescriptor)
    }
}

RelevantEntities: Register relevant entities

([5:06](https://developer.apple.com/videos/play/wwdc2026/345/?time=306))

The RelevantEntities API lets you tell the system which entities are relevant in which contexts:

// Recommend playlists for running workouts
let playlistEntities = [dailyRun, runningMix]
let workoutContext = AppEntityContext.audio(.workout(activityType: .running))

try await RelevantEntities.shared.updateEntities(
    playlistEntities, for: workoutContext
)

// Clear all entities for a specific context
try await RelevantEntities.shared.removeAllEntities(for: workoutContext)

// Remove specific entities from a specific context
try await RelevantEntities.shared.removeEntities(playlistEntities, from: workoutContext)

// Remove all entities from all contexts
try await RelevantEntities.shared.removeAllEntities()

Key points:

  • AppEntityContext.audio(.workout(activityType:)) defines an audio-related fitness context
  • updateEntities(_:for:) registers entities so the system can recommend them in the right situations
  • Entities remain registered until you actively remove them
  • removeAllEntities() can clear by context or globally

Choosing among three content discovery approaches:

  • Spotlight indexing: Make content searchable and retrievable through Siri
  • Interaction donation: Teach Siri how users use your app so it can recognize patterns and suggest repeated actions
  • RelevantEntities: Tell the system which content is relevant in a specific context

EntityCollection: Efficiently handle large entity sets

([7:12](https://developer.apple.com/videos/play/wwdc2026/345/?time=432))

Change the parameter type from an array to EntityCollection, and the system passes only IDs instead of full entities:

struct TagPhotosIntent: AppIntent {
    static let title: LocalizedStringResource = "Tag Travel Photos"

    @Parameter var photos: EntityCollection<PhotoEntity>   // Previously: [PhotoEntity]
    @Parameter var tag: String

    func perform() async throws -> some IntentResult {
        modelData.tagPhotos(ids: photos.identifiers, tag: tag)   // Previously: tagPhotos(photos, tag: tag)
        return .result()
    }
}

Key points:

  • EntityCollection<PhotoEntity> replaces [PhotoEntity]
  • photos.identifiers contains only IDs, not complete entity data
  • This avoids the performance cost of resolving hundreds or thousands of entities
  • Your data model method only needs IDs to update records

SyncableEntity: Stable IDs across devices

([9:49](https://developer.apple.com/videos/play/wwdc2026/345/?time=589))

If your ID is already stable across devices, such as a server UUID or CloudKit record ID:

struct PhotoEntity: AppEntity, SyncableEntity {
    var id: String  // Already stable across devices; this is enough
}

If you use local IDs, pair them with a stable ID:

struct PhotoEntity: AppEntity, SyncableEntity {
    var id: SyncableEntityIdentifier<String, String>

    init(localID: String, stableID: String) {
        self.id = SyncableEntityIdentifier(local: localID, stable: stableID)
    }
}

Key points:

  • The SyncableEntity protocol declares that the ID is stable across devices
  • If the ID itself is stable, adding the protocol is enough
  • SyncableEntityIdentifier<Local, Stable> pairs a local ID with a stable ID
  • Device-local code uses the local ID, while cross-device system behavior uses the stable ID

@UnionValue: Accept multiple types

([11:58](https://developer.apple.com/videos/play/wwdc2026/345/?time=718))

Allow one parameter to accept multiple entity types:

@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"
    ]
}

Key points:

  • The @UnionValue macro generates the required type information and picker support
  • Each case wraps a different entity type
  • typeDisplayRepresentation is the label for the overall type
  • caseDisplayRepresentations defines how each option appears in the picker
  • It also appears as multiple options in the Shortcuts app

LongRunningIntent + CancellableIntent: Long-running tasks

([13:41](https://developer.apple.com/videos/play/wwdc2026/345/?time=821))

Let an intent run for more than 30 seconds and support cancellation:

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)")
    }
}

Key points:

  • LongRunningIntent allows execution beyond the 30-second limit
  • performBackgroundTask wraps work that needs a long time to run
  • The progress object comes from ProgressReportingIntent, and the system uses it to show progress
  • Progress is automatically shown as a Live Activity
  • Task.checkCancellation() checks whether the task has been canceled
  • CancellableIntent handles cleanup in onCancel
  • Users can tap the stop button in the Live Activity

ExecutionTargets: Control the execution process

([16:54](https://developer.apple.com/videos/play/wwdc2026/345/?time=1014))

Specify which process an intent runs in:

// 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 }
}

// Read-only display: runs in the widget extension
struct GetLandmarkStatusIntent: AppIntent {
    static var allowedExecutionTargets: ExecutionTargets { .widgetKitExtension }
}

// Either one is fine: let the system choose
struct TagPhotosIntent: AppIntent {
    static var allowedExecutionTargets: ExecutionTargets { [.main, .appIntentsExtension] }
}

Key points:

  • The static allowedExecutionTargets property overrides the system’s heuristic choice
  • .main specifies the main app
  • .appIntentsExtension specifies the App Intents extension
  • .widgetKitExtension specifies the Widget extension
  • You can specify multiple options and let the system choose among them

Key Takeaways

1. Landmark sharing apps

  • What to do: Let users send landmarks from your app directly to Maps for navigation, or to Calendar as an event location.
  • Why it is worth doing: ValueRepresentation lets your entities carry system-known types such as PlaceDescriptor and EventDescriptor, integrating seamlessly with system apps such as Maps and Calendar.
  • How to start: Add ValueRepresentation to your entity and export the corresponding Descriptor type.

2. Context-aware music apps

  • What to do: Automatically recommend suitable playlists based on the user’s current activity, such as running, reading, or sleeping.
  • Why it is worth doing: RelevantEntities lets you tell the system, “This playlist is relevant while running,” so the system can recommend it in a fitness app’s workout setup UI.
  • How to start: Define a context with AppEntityContext, then register entities with RelevantEntities.shared.updateEntities(_:for:).

3. Batch photo management apps

  • What to do: Let users tag hundreds or thousands of photos at once or add them to an album without lag.
  • Why it is worth doing: EntityCollection passes only IDs, avoiding the performance cost of resolving large numbers of entities and making batch operations complete quickly.
  • How to start: Change an array-receiving @Parameter to EntityCollection<YourEntityType>, then use the identifiers property to get the ID array.

4. Cloud-synced notes apps

  • What to do: Let users say “Add this to my to-do list” for a note on iPhone, then say “Mark it complete” for the same note on Mac.
  • Why it is worth doing: SyncableEntity lets Siri refer to the same entity across devices and enables true cross-device conversations.
  • How to start: Make sure the entity has a stable cross-device ID, such as a server UUID or CloudKit record ID, then add SyncableEntity.

5. Large file upload apps

  • What to do: Let users trigger large file uploads from a Widget, show real-time progress, and cancel at any time.
  • Why it is worth doing: LongRunningIntent lets tasks run longer than 30 seconds, progress is automatically shown as a Live Activity, and CancellableIntent enables graceful cleanup on cancellation.
  • How to start: Implement LongRunningIntent and CancellableIntent, wrap the work with performBackgroundTask, and update progress regularly.

Comments

GitHub Issues · utterances