App Intents en Siri Shortcuts: geavanceerde iOS-automatisering 2026

Volledige gids over App Intents en Siri Shortcuts voor iOS 18+. Eigen Siri-acties bouwen, Apple Intelligence integreren en de Swift-app automatiseren in 2026.

App Intents en Siri Shortcuts voor geavanceerde iOS-automatisering met Swift en Apple Intelligence

In 2026 betreden iOS-applicaties met Apple Intelligence en het App Intents-framework een tijdperk waarin gebruikersintentie belangrijker is dan de grafische interface. Apps zonder intents worden onzichtbaar in een AI-georiënteerd besturingssysteem. App Intents vormen het fundament dat Siri, Spotlight, widgets en de Action Button in staat stelt te interageren met de app-functionaliteit.

Wat dit artikel behandelt

Dit artikel toont de volledige creatie van App Intents en Siri Shortcuts voor iOS 18+, van fundamentele concepten tot integratie met Apple Intelligence en App Intent Domains.

Het App Intents-framework begrijpen

Het App Intents-framework, geïntroduceerd met iOS 16, moderniseert de constructie van intents in Swift en vervangt het oudere SiriKit Intents-framework. Deze declaratieve architectuur maakt het mogelijk acties te creëren die door het systeem ontdekt worden: Spotlight, Opdrachten-app, Siri en Action Button.

TaskIntent.swiftswift
import AppIntents

// Een AppIntent vertegenwoordigt een actie die de gebruiker kan uitvoeren
struct CreateTaskIntent: AppIntent {
    // Titel weergegeven in Opdrachten en Siri
    static var title: LocalizedStringResource = "Taak aanmaken"

    // Beschrijving voor toegankelijkheid en suggesties
    static var description = IntentDescription(
        "Maakt een nieuwe taak aan in de app."
    )

    // Parameter met automatische validatie
    @Parameter(title: "Taaktitel")
    var taskTitle: String

    // Optionele parameter met standaardwaarde
    @Parameter(title: "Prioriteit", default: .medium)
    var priority: TaskPriority

    // Uitvoering van de actie
    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        // Maak de taak via de service
        let task = TaskService.shared.createTask(
            title: taskTitle,
            priority: priority
        )

        // Retourneer de aangemaakte entity voor chaining
        return .result(value: TaskEntity(task: task))
    }
}

De intent declareert zijn parameters via de @Parameter property wrapper, waardoor Siri ontbrekende waarden kan opvragen. De perform()-methode voert de bedrijfslogica uit en retourneert een getypeerd resultaat.

App Entities definiëren voor data

App Entities vertegenwoordigen de "zelfstandige naamwoorden" van de applicatie: de objecten waarop intents werken. Ze stellen Siri in staat de app-data te begrijpen en te manipuleren.

TaskEntity.swiftswift
import AppIntents

// Intern datamodel
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool
    var dueDate: Date?
}

// Entity blootgesteld aan het systeem
struct TaskEntity: AppEntity {
    // Vereiste unieke identifier
    var id: UUID

    // Weergeefbare eigenschappen
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool

    // Weergaveconfiguratie in het systeem
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Taak"

    // Visuele representatie van de instantie
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(title)",
            subtitle: "\(priority.rawValue)",
            image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
        )
    }

    // Standaard query voor het zoeken van entities
    static var defaultQuery = TaskEntityQuery()

    // Initializer vanuit het interne model
    init(task: Task) {
        self.id = task.id
        self.title = task.title
        self.priority = task.priority
        self.isCompleted = task.isCompleted
    }
}

// Query om entities te zoeken en filteren
struct TaskEntityQuery: EntityQuery {
    // Zoeken op identifiers
    func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { identifiers.contains($0.id) }
            .map { TaskEntity(task: $0) }
    }

    // Suggesties getoond in de interface
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { !$0.isCompleted }
            .prefix(5)
            .map { TaskEntity(task: $0) }
    }
}

De EntityQuery definieert hoe het systeem entities zoekt en voorstelt. De methoden entities(for:) en suggestedEntities() voeden de Siri- en Opdrachten-interfaces.

App Enums vs App Entities

Gebruik AppEnum voor types met een vaste verzameling waarden (prioriteit, status), en AppEntity voor dynamische, door gebruikers aangemaakte types (taken, notities, contacten).

App Enums maken voor vaste waarden

App Enums stellen opgesomde types beschikbaar aan het systeem, waardoor Siri contextuele keuzes kan aanbieden.

TaskPriority.swiftswift
import AppIntents

// Enum blootgesteld aan het systeem
enum TaskPriority: String, AppEnum, Codable {
    case low
    case medium
    case high

    // Weergegeven typenaam
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioriteit"

    // Representatie van elk geval
    static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
        .low: DisplayRepresentation(
            title: "Laag",
            image: .init(systemName: "arrow.down.circle")
        ),
        .medium: DisplayRepresentation(
            title: "Gemiddeld",
            image: .init(systemName: "minus.circle")
        ),
        .high: DisplayRepresentation(
            title: "Hoog",
            image: .init(systemName: "exclamationmark.circle")
        )
    ]
}

// Enum voor de status van de taak
enum TaskStatus: String, AppEnum, Codable {
    case pending
    case inProgress
    case completed

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Status"

    static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [
        .pending: "In afwachting",
        .inProgress: "Bezig",
        .completed: "Voltooid"
    ]
}

Visuele representaties (SF Symbols-iconen) verrijken de weergave in Opdrachten en Siri-suggesties.

AppShortcutsProvider implementeren

De AppShortcutsProvider stelt App Shortcuts beschikbaar aan het systeem, direct beschikbaar zonder gebruikersconfiguratie. Deze shortcuts verschijnen in Spotlight, Siri en de Action Button.

ShortcutsProvider.swiftswift
import AppIntents

// Provider die alle app-shortcuts declareert
struct TaskAppShortcutsProvider: AppShortcutsProvider {
    // Maximaal 10 shortcuts per applicatie
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // Shortcut om een taak aan te maken
        AppShortcut(
            intent: CreateTaskIntent(),
            phrases: [
                // De .applicationName placeholder is VERPLICHT
                "Taak aanmaken met \(.applicationName)",
                "Nieuwe taak in \(.applicationName)",
                "Taak toevoegen aan \(.applicationName)"
            ],
            shortTitle: "Taak aanmaken",
            systemImageName: "plus.circle"
        )

        // Shortcut om taken op te lijsten
        AppShortcut(
            intent: ListTasksIntent(),
            phrases: [
                "Toon mijn taken in \(.applicationName)",
                "Geef taken weer van \(.applicationName)",
                "Wat zijn mijn taken \(.applicationName)"
            ],
            shortTitle: "Mijn taken",
            systemImageName: "list.bullet"
        )

        // Shortcut met dynamische parameter
        AppShortcut(
            intent: CompleteTaskIntent(),
            phrases: [
                "Voltooi \(\.$taskName) in \(.applicationName)",
                "Markeer \(\.$taskName) als gedaan met \(.applicationName)"
            ],
            shortTitle: "Taak voltooien",
            systemImageName: "checkmark.circle"
        )
    }
}

Spraakzinnen moeten de placeholder \(.applicationName) bevatten zodat Siri de doelapp identificeert. Dynamische parameters zoals \(\.$taskName) maken contextuele commando's mogelijk.

Limiet van 10 Shortcuts

Een applicatie kan maximaal 10 App Shortcuts declareren. Het is verstandig de meest frequente en nuttige acties voor gebruikers te prioriteren.

Intents met complexe parameters

Intents kunnen complexe parameters accepteren, inclusief entities en configuratie-opties.

CompleteTaskIntent.swiftswift
import AppIntents

struct CompleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Taak voltooien"

    static var description = IntentDescription(
        "Markeert een taak als voltooid."
    )

    // Entity-parameter met automatisch zoeken
    @Parameter(title: "Taak")
    var task: TaskEntity

    // Optionele parameter met datum
    @Parameter(title: "Voltooiingsdatum")
    var completionDate: Date?

    // Configuratie van de Siri-dialoog
    static var parameterSummary: some ParameterSummary {
        Summary("Voltooi \(\.$task)") {
            \.$completionDate
        }
    }

    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Werk de taak bij
        TaskService.shared.completeTask(
            id: task.id,
            completionDate: completionDate ?? Date()
        )

        // Spraakfeedback voor Siri
        return .result(
            dialog: "Taak \(task.title) is gemarkeerd als voltooid."
        )
    }
}

// Intent die een lijst van entities retourneert
struct ListTasksIntent: AppIntent {
    static var title: LocalizedStringResource = "Taken oplijsten"

    // Optioneel filter
    @Parameter(title: "Status", default: nil)
    var statusFilter: TaskStatus?

    @Parameter(title: "Prioriteit", default: nil)
    var priorityFilter: TaskPriority?

    func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> {
        var tasks = TaskService.shared.fetchTasks()

        // Pas filters toe
        if let status = statusFilter {
            tasks = tasks.filter {
                switch status {
                case .completed: return $0.isCompleted
                case .pending, .inProgress: return !$0.isCompleted
                }
            }
        }

        if let priority = priorityFilter {
            tasks = tasks.filter { $0.priority == priority }
        }

        let entities = tasks.map { TaskEntity(task: $0) }
        return .result(value: entities)
    }
}

ParameterSummary definieert hoe Siri de intent presenteert tijdens spraakuitvoering en biedt natuurlijke feedback.

Klaar om je iOS gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Apple Intelligence-integratie

iOS 18 introduceert App Intent Domains, verzamelingen van API's ontworpen voor specifieke functionaliteiten. Deze domeinen stellen Apple Intelligence in staat acties met grotere precisie te begrijpen en uit te voeren.

BookmarkDomainIntents.swiftswift
import AppIntents

// Conformiteit met het Bookmarks-domein voor Apple Intelligence-integratie
struct SaveBookmarkIntent: AppIntent {
    static var title: LocalizedStringResource = "Bladwijzer opslaan"

    // URL-parameter met automatische validatie
    @Parameter(title: "URL")
    var url: URL

    @Parameter(title: "Titel", default: nil)
    var title: String?

    @Parameter(title: "Map", default: nil)
    var folder: BookmarkFolderEntity?

    // Open app indien nodig
    static var openAppWhenRun: Bool = false

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let bookmark = BookmarkService.shared.save(
            url: url,
            title: title,
            folder: folder?.id
        )

        return .result(
            dialog: "Bladwijzer opgeslagen: \(bookmark.title)"
        )
    }
}

// Intent met bewustzijn van schermcontent (iOS 18.4+)
struct AnalyzeScreenContentIntent: AppIntent {
    static var title: LocalizedStringResource = "Inhoud analyseren"

    // Toegang tot schermcontext via Apple Intelligence
    @Parameter(title: "Context")
    var screenContext: String?

    func perform() async throws -> some IntentResult & ProvidesDialog {
        guard let context = screenContext else {
            return .result(dialog: "Geen inhoud om te analyseren.")
        }

        // Verwerk inhoud geëxtraheerd door Apple Intelligence
        let analysis = ContentAnalyzer.analyze(context)

        return .result(dialog: analysis.summary)
    }
}

Vooraf gedefinieerde domeinen (Books, Camera, Spreadsheets) stellen Siri in staat nauwkeurig te reageren op verzoeken dankzij modellen getraind op deze specifieke taken.

Siri-acties met gebruikersbevestiging

Voor gevoelige acties kan het systeem bevestiging vragen vóór de uitvoering.

DeleteTaskIntent.swiftswift
import AppIntents

struct DeleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Taak verwijderen"

    @Parameter(title: "Taak")
    var task: TaskEntity

    // Vereist gebruikersbevestiging
    static var isDiscoverable: Bool = true

    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Vraag bevestiging via dialoog
        try await requestConfirmation(
            result: .result(
                dialog: "Weet je zeker dat je \(task.title) wilt verwijderen?"
            )
        )

        // Verwijder na bevestiging
        TaskService.shared.deleteTask(id: task.id)

        return .result(
            dialog: "Taak \(task.title) is verwijderd."
        )
    }
}

// Intent met meerdere dialoogstappen
struct ScheduleTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Taak plannen"

    @Parameter(title: "Taak")
    var task: TaskEntity

    @Parameter(title: "Datum")
    var scheduledDate: Date

    @Parameter(title: "Herinnering", default: true)
    var setReminder: Bool

    static var parameterSummary: some ParameterSummary {
        When(\.$setReminder, .equalTo, true) {
            Summary("Plan \(\.$task) voor \(\.$scheduledDate) met herinnering")
        } otherwise: {
            Summary("Plan \(\.$task) voor \(\.$scheduledDate)")
        }
    }

    func perform() async throws -> some IntentResult & ProvidesDialog {
        TaskService.shared.schedule(
            taskId: task.id,
            date: scheduledDate,
            reminder: setReminder
        )

        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .medium
        dateFormatter.timeStyle = .short

        let formattedDate = dateFormatter.string(from: scheduledDate)

        return .result(
            dialog: "Taak gepland voor \(formattedDate)."
        )
    }
}

De requestConfirmation-methode pauzeert de uitvoering tot gebruikersvalidatie en beschermt tegen onbedoelde acties.

Intents in interactieve widgets

App Intents integreren naadloos met WidgetKit om interactieve widgets te maken op iOS 17+.

TaskWidgetIntents.swiftswift
import AppIntents
import WidgetKit

// Intent geoptimaliseerd voor widget (snelle uitvoering)
struct ToggleTaskFromWidgetIntent: AppIntent {
    static var title: LocalizedStringResource = "Taak omschakelen"

    @Parameter(title: "Taak-ID")
    var taskID: String

    init() {}

    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // Geen dialoog voor widgets
    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        TaskService.shared.toggleCompletion(taskId: uuid)

        // Onmiddellijke widget-vernieuwing
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

// Widget-view met interactieve knop
import SwiftUI

struct TaskWidgetView: View {
    let task: Task

    var body: some View {
        Button(intent: ToggleTaskFromWidgetIntent(taskID: task.id)) {
            HStack {
                Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                    .foregroundStyle(task.isCompleted ? .green : .secondary)

                Text(task.title)
                    .strikethrough(task.isCompleted)
            }
            .padding()
        }
        .buttonStyle(.plain)
    }
}

Widgets gebruiken de syntaxis Button(intent:) om de interactie direct te koppelen aan het App Intent, zonder de applicatie te openen.

Action Button-configuratie

De Action Button op iPhone 15 Pro en later kan App Shortcuts direct activeren.

ActionButtonIntent.swiftswift
import AppIntents

// Intent geoptimaliseerd voor Action Button
struct QuickCaptureIntent: AppIntent {
    static var title: LocalizedStringResource = "Snelle vastlegging"

    static var description = IntentDescription(
        "Maakt snel een taak met een titel."
    )

    // Opent app voor invoer
    static var openAppWhenRun: Bool = true

    func perform() async throws -> some IntentResult & OpensIntent {
        // Notificatie om snelle vastleggingsscherm te openen
        NotificationCenter.default.post(
            name: .quickCaptureTriggered,
            object: nil
        )

        return .result(opensIntent: ShowQuickCaptureViewIntent())
    }
}

// Declareren in AppShortcutsProvider voor Action Button
extension TaskAppShortcutsProvider {
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // ... andere shortcuts

        AppShortcut(
            intent: QuickCaptureIntent(),
            phrases: [
                "Snelle vastlegging \(.applicationName)",
                "Snelle notitie \(.applicationName)"
            ],
            shortTitle: "Vastleggen",
            systemImageName: "bolt.circle"
        )
    }
}

Gebruikers kunnen de Action Button configureren om deze shortcut te activeren via Instellingen > Action Button.

iOS 18+ Macros

iOS 18 introduceert de macros @DeferredProperty en @ComputedProperty om boilerplate te verminderen. App Intents kunnen ook in Swift Packages staan voor cross-platform hergebruik.

Intents in Swift Packages

App Intents kunnen worden gedefinieerd in Swift Packages voor delen tussen iOS, macOS en watchOS.

Package.swiftswift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "TaskIntents",
    platforms: [
        .iOS(.v17),
        .macOS(.v14),
        .watchOS(.v10)
    ],
    products: [
        .library(name: "TaskIntents", targets: ["TaskIntents"])
    ],
    dependencies: [],
    targets: [
        .target(
            name: "TaskIntents",
            dependencies: []
        )
    ]
)

// Sources/TaskIntents/SharedIntents.swift
import AppIntents

// Cross-platform gedeelde intent
public struct SharedCreateTaskIntent: AppIntent {
    public static var title: LocalizedStringResource = "Taak aanmaken"

    @Parameter(title: "Titel")
    public var taskTitle: String

    public init() {}

    public func perform() async throws -> some IntentResult {
        // Gedeelde logica
        await TaskRepository.shared.create(title: taskTitle)
        return .result()
    }
}

// Platform-specifieke uitbreiding in app
#if os(iOS)
extension SharedCreateTaskIntent {
    // iOS-specifiek gedrag
    static var openAppWhenRun: Bool = false
}
#endif

Deze architectuur behoudt één codebase voor de intents en past het gedrag per platform aan.

App Intents testen

App Intents kunnen unit-getest worden zoals elke Swift-code.

TaskIntentTests.swiftswift
import XCTest
import AppIntents
@testable import TaskApp

final class TaskIntentTests: XCTestCase {

    override func setUp() {
        super.setUp()
        // Reset service voor geïsoleerde tests
        TaskService.shared.reset()
    }

    func testCreateTaskIntent() async throws {
        // Given
        var intent = CreateTaskIntent()
        intent.taskTitle = "Test taak"
        intent.priority = .high

        // When
        let result = try await intent.perform()

        // Then
        let tasks = TaskService.shared.fetchTasks()
        XCTAssertEqual(tasks.count, 1)
        XCTAssertEqual(tasks.first?.title, "Test taak")
        XCTAssertEqual(tasks.first?.priority, .high)
    }

    func testCompleteTaskIntent() async throws {
        // Given
        let task = TaskService.shared.createTask(
            title: "Taak te voltooien",
            priority: .medium
        )

        var intent = CompleteTaskIntent()
        intent.task = TaskEntity(task: task)

        // When
        _ = try await intent.perform()

        // Then
        let updatedTask = TaskService.shared.fetchTask(id: task.id)
        XCTAssertTrue(updatedTask?.isCompleted ?? false)
    }

    func testEntityQuery() async throws {
        // Given
        let task1 = TaskService.shared.createTask(title: "Taak 1", priority: .low)
        let task2 = TaskService.shared.createTask(title: "Taak 2", priority: .high)

        let query = TaskEntityQuery()

        // When
        let entities = try await query.entities(for: [task1.id, task2.id])

        // Then
        XCTAssertEqual(entities.count, 2)
    }
}

Tests verifiëren het gedrag van intents onafhankelijk van de systeeminterface.

Best practices en optimalisaties

Verschillende patronen zorgen voor performante App Intents en een optimale gebruikerservaring.

BestPractices.swiftswift
import AppIntents

// 1. Gelokaliseerde zinnen
struct LocalizedTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "task.create.title"

    static var description = IntentDescription(
        "task.create.description",
        categoryName: "task.category"
    )

    @Parameter(title: "task.parameter.title")
    var taskTitle: String

    func perform() async throws -> some IntentResult {
        // ...
        return .result()
    }
}

// 2. Correcte foutafhandeling
enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible {
    case taskNotFound
    case invalidInput
    case serviceUnavailable

    var localizedStringResource: LocalizedStringResource {
        switch self {
        case .taskNotFound:
            return "Taak niet gevonden."
        case .invalidInput:
            return "Ongeldige gegevens."
        case .serviceUnavailable:
            return "Dienst tijdelijk niet beschikbaar."
        }
    }
}

struct RobustTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Robuuste intent"

    @Parameter(title: "ID")
    var taskId: String

    func perform() async throws -> some IntentResult & ProvidesDialog {
        guard let uuid = UUID(uuidString: taskId) else {
            throw TaskIntentError.invalidInput
        }

        guard let task = TaskService.shared.fetchTask(id: uuid) else {
            throw TaskIntentError.taskNotFound
        }

        return .result(dialog: "Taak gevonden: \(task.title)")
    }
}

// 3. EntityQuery-optimalisatie
struct OptimizedTaskQuery: EntityStringQuery {
    // Geoptimaliseerde tekstuele zoekopdracht
    func entities(matching string: String) async throws -> [TaskEntity] {
        // Service-side zoeken met limiet
        TaskService.shared.search(query: string, limit: 10)
            .map { TaskEntity(task: $0) }
    }

    // Beperkte suggesties voor performance
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchRecentTasks(limit: 5)
            .map { TaskEntity(task: $0) }
    }
}

// 4. Focus Filter voor Focus-modi
struct TaskFocusFilter: SetFocusFilterIntent {
    static var title: LocalizedStringResource = "Taken filteren"

    @Parameter(title: "Toon alleen hoge prioriteit")
    var showHighPriorityOnly: Bool

    func perform() async throws -> some IntentResult {
        TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly)
        return .result()
    }
}

Deze patronen zorgen voor een vlotte systeemintegratie met behoud van optimale performance.

Conclusie

App Intents en Siri Shortcuts transformeren de manier waarop gebruikers met iOS-applicaties interageren. In 2026, met Apple Intelligence, is het blootstellen van intents niet langer optioneel maar essentieel om een moderne en intuïtieve ervaring te leveren.

Checklist App Intents iOS 18+

  • ✅ AppIntents aanmaken voor de hoofdacties van de app
  • ✅ AppEntities definiëren voor manipuleerbare data
  • ✅ AppEnum gebruiken voor opgesomde types
  • ✅ AppShortcutsProvider implementeren met spraakzinnen
  • ✅ Het maximum van 10 App Shortcuts respecteren
  • \(.applicationName) opnemen in alle zinnen
  • ✅ ParameterSummary configureren voor Siri-feedback
  • ✅ Apple Intelligence-domeinen integreren indien van toepassing
  • ✅ Intents testen met unit-tests
  • ✅ Titels en beschrijvingen lokaliseren

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

#app-intents
#siri-shortcuts
#ios
#swift
#apple-intelligence

Delen

Gerelateerde artikelen