Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2025 AI & Machine Learning

WWDC25 · 27 min · AI & Machine Learning

Explore new advances in App Intents

Explore all the new enhancements available in the App Intents framework in this year’s releases. Learn about developer quality-of-life improvements like deferred properties, new capabilities like interactive app intents snippets, entity view annotations, how to integrate Visual Intelligence, and much more. We’ll take you through how App Intents is more expressive than ever, while becoming even easier and smoother to adopt. We’ll also share exciting new clients of App Intents this year like Spotlight and Visual Intelligence, and learn to write app intents that work great in those contexts.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 23 snippets

Returning a Snippet Intent swift · at 4:08 ↗
import AppIntents
import SwiftUI

struct ClosestLandmarkIntent: AppIntent {
    static let title: LocalizedStringResource = "Find Closest Landmark"

    @Dependency var modelData: ModelData

    func perform() async throws -> some ReturnsValue<LandmarkEntity> & ShowsSnippetIntent & ProvidesDialog {
        let landmark = await self.findClosestLandmark()

        return .result(
            value: landmark,
            dialog: IntentDialog(
                full: "The closest landmark is \(landmark.name).",
                supporting: "\(landmark.name) is located in \(landmark.continent)."
            ),
            snippetIntent: LandmarkSnippetIntent(landmark: landmark)
        )
    }
}
Building a SnippetIntent swift · at 4:31 ↗
struct LandmarkSnippetIntent: SnippetIntent {
    static let title: LocalizedStringResource = "Landmark Snippet"

    @Parameter var landmark: LandmarkEntity
    @Dependency var modelData: ModelData

    func perform() async throws -> some IntentResult & ShowsSnippetView {
        let isFavorite = await modelData.isFavorite(landmark)

        return .result(
            view: LandmarkView(landmark: landmark, isFavorite: isFavorite)
        )
    }
}
Associate intents with buttons swift · at 5:45 ↗
struct LandmarkView: View {
    let landmark: LandmarkEntity
    let isFavorite: Bool

    var body: some View {
        // ...
        Button(intent: UpdateFavoritesIntent(landmark: landmark, isFavorite: !isFavorite)) { /* ... */ }

        Button(intent: FindTicketsIntent(landmark: landmark)) { /* ... */ }
        // ...
    }
}
Request confirmation snippet swift · at 6:53 ↗
struct FindTicketsIntent: AppIntent {

    func perform() async throws -> some IntentResult & ShowsSnippetIntent {
        let searchRequest = await searchEngine.createRequest(landmarkEntity: landmark)

        // Present a snippet that allows people to change
        // the number of tickets.
        try await requestConfirmation(
            actionName: .search,
            snippetIntent: TicketRequestSnippetIntent(searchRequest: searchRequest)
        )

        // Resume searching...
    }
}
Using Entities as parameters swift · at 7:24 ↗
struct TicketRequestSnippetIntent: SnippetIntent {
    static let title: LocalizedStringResource = "Ticket Request Snippet"

    @Parameter var searchRequest: SearchRequestEntity

    func perform() async throws -> some IntentResult & ShowsSnippetView {
        let view = TicketRequestView(searchRequest: searchRequest)

        return .result(view: view)
    }
}
Updating a snippet swift · at 8:01 ↗
func performRequest(request: SearchRequestEntity) async throws {
    // Set to pending status...
   
    TicketResultSnippetIntent.reload()

    // Kick off search...

    TicketResultSnippetIntent.reload()
}
Responding to Image Search swift · at 9:24 ↗
struct LandmarkIntentValueQuery: IntentValueQuery {

    @Dependency var modelData: ModelData

    func values(for input: SemanticContentDescriptor) async throws -> [LandmarkEntity] {
        guard let pixelBuffer: CVReadOnlyPixelBuffer = input.pixelBuffer else {
            return []
        }

        let landmarks = try await modelData.searchLandmarks(matching: pixelBuffer)

        return landmarks
    }
}
Support opening an entity swift · at 9:51 ↗
struct OpenLandmarkIntent: OpenIntent {
    static var title: LocalizedStringResource = "Open Landmark"

    @Parameter(title: "Landmark")
    var target: LandmarkEntity

    func perform() async throws -> some IntentResult {
        /// ...
    }
}
Show search results in app swift · at 10:53 ↗
@AppIntent(schema: .visualIntelligence.semanticContentSearch)
struct ShowSearchResultsIntent {
    var semanticContent: SemanticContentDescriptor

    @Dependency var navigator: Navigator

    func perform() async throws -> some IntentResult {
        await navigator.showImageSearch(semanticContent.pixelBuffer)

        return .result()
    }

    // ...
}
Returning multiple entity types swift · at 11:40 ↗
@UnionValue
enum VisualSearchResult {
    case landmark(LandmarkEntity)
    case collection(CollectionEntity)
}a

struct LandmarkIntentValueQuery: IntentValueQuery {
    func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] {
        // ...
    }
}

struct OpenLandmarkIntent: OpenIntent { /* ... */ }
struct OpenCollectionIntent: OpenIntent { /* ... */ }
Associating a view with an AppEntity swift · at 13:00 ↗
struct LandmarkDetailView: View {

    let landmark: LandmarkEntity

    var body: some View {
        Group{ /* ... */ }
        .userActivity("com.landmarks.ViewingLandmark") { activity in
            activity.title = "Viewing \(landmark.name)"
            activity.appEntityIdentifier = EntityIdentifier(for: landmark)
        }
    }
}
Converting AppEntity to PDF swift · at 13:21 ↗
import CoreTransferable
import PDFKit

extension LandmarkEntity: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(exportedContentType: .pdf) {landmark in
            // Create PDF data...
            return data
        }
    }
}
Associating properties with Spotlight keys swift · at 14:05 ↗
struct LandmarkEntity: IndexedEntity {

    // ...

    @Property(indexingKey: \.displayName)
    var name: String

    @Property(customIndexingKey: /* ... */)
    var continent: String

    // ...
}
Making intents undoable swift · at 15:49 ↗
struct DeleteCollectionIntent: UndoableIntent {
    // ...

    func perform() async throws -> some IntentResult {

        // Confirm deletion...

        await undoManager?.registerUndo(withTarget: modelData) {modelData in
            // Restore collection...
        }
        await undoManager?.setActionName("Delete \(collection.name)")

       // Delete collection...
    }
}
Multiple choice swift · at 16:52 ↗
struct DeleteCollectionIntent: UndoableIntent {
    func perform() async throws -> some IntentResult & ReturnsValue<CollectionEntity?> {
        let archive = Option(title: "Archive", style: .default)
        let delete = Option(title: "Delete", style: .destructive)

        let resultChoice = try await requestChoice(
            between: [.cancel, archive, delete],
            dialog: "Do you want to archive or delete \(collection.name)?",
            view: collectionSnippetView(collection)
        )

        switch resultChoice {
        case archive: // Archive collection...
        case delete: // Delete collection...
        default: // Do nothing...
        }
    }
    // ...
}
Supported modes swift · at 18:47 ↗
struct GetCrowdStatusIntent: AppIntent {

    static let supportedModes: IntentModes = [.background, .foreground]

    func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog {
        if systemContext.currentMode == .foreground {
            await navigator.navigateToCrowdStatus(landmark)
        }

        // Retrieve status and return dialog...
    }
}
Supported modes swift · at 19:30 ↗
struct GetCrowdStatusIntent: AppIntent {
    static let supportedModes: IntentModes = [.background, .foreground(.dynamic)]

    func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog {
        guard await modelData.isOpen(landmark) else { /* Exit early... */ }

        if systemContext.currentMode.canContinueInForeground {
            do {
                try await continueInForeground(alwaysConfirm: false)
                await navigator.navigateToCrowdStatus(landmark)
            } catch {
                // Open app denied.
            }
        }

        // Retrieve status and return dialog...
    }
}
View Control swift · at 21:30 ↗
extension OpenLandmarkIntent: TargetContentProvidingIntent {}

struct LandmarksNavigationStack: View {

    @State var path: [Landmark] = []

    var body: some View {
        NavigationStack(path: $path) { /* ... */ }
        .onAppIntentExecution(OpenLandmarkIntent.self) { intent in
            self.path.append(intent.landmark)
        }
    }
}
Scene activation condition swift · at 23:13 ↗
@main
struct AppIntentsTravelTrackerApp: App {
    var body: some Scene {
        WindowGroup { /* ... */ }

        WindowGroup { /* ... */ }
        .handlesExternalEvents(matching: [
            OpenLandmarkIntent.persistentIdentifier
        ])
    }
}
View activation condition swift · at 23:33 ↗
struct LandmarksNavigationStack: View {
    var body: some View {
        NavigationStack(path: $path) { /* ... */ }
        .handlesExternalEvents(
            preferring: [],
            allowing: !isEditing ? [OpenLandmarkIntent.persistentIdentifier] : []
        )
    }
}
Computed property swift · at 24:23 ↗
struct SettingsEntity: UniqueAppEntity {

    @ComputedProperty
    var defaultPlace: PlaceDescriptor {
        UserDefaults.standard.defaultPlace
    }

    init() {
    }
}
Deferred property swift · at 24:48 ↗
struct LandmarkEntity: IndexedEntity {
    // ...

    @DeferredProperty
    var crowdStatus: Int {
        get async throws {
            await modelData.getCrowdStatus(self)
        }
    }

    // ...
}
AppIntentsPackage swift · at 25:50 ↗
// Framework or dynamic library
public struct LandmarksKitPackage: AppIntentsPackage { }

// App target
struct LandmarksPackage: AppIntentsPackage {
    static var includedPackages: [any AppIntentsPackage.Type] {
        [LandmarksKitPackage.self]
    }
}

Resources