App Intents i SwiftUI: Komplett guide till Siri, Spotlight och Apple Intelligence i iOS 26

Lär dig bygga App Intents i SwiftUI för iOS 26 — från Siri-genvägar och Spotlight till Visual Intelligence, interaktiva snippets och on-screen entities med praktiska kodexempel.

App Intents iOS 26: Siri & Spotlight Guide

Vad är App Intents?

App Intents är Apples moderna Swift-nativa ramverk som låter dig exponera din apps funktionalitet för systemtjänster som Siri, Spotlight, Genvägar-appen, Action-knappen och widgetar. Ramverket dök upp med iOS 16 på WWDC22 och har sedan dess vuxit till att bli den centrala kopplingen mellan din app och resten av Apple-ekosystemet.

Det som gör App Intents så mycket trevligare att jobba med jämfört med gamla SiriKit är att du slipper definiera intents i separata .intentdefinition-filer och köra dem i extensions. Istället bygger allt på ren Swift-kod. Din kod är den enda källan till sanning — Xcode extraherar metadata automatiskt vid byggtid. Mindre boilerplate, enklare underhåll. Helt enkelt.

Varför App Intents spelar roll 2026

Med iOS 26 har App Intents fått rejäla uppgraderingar: stöd för Visual Intelligence, interaktiva snippets, on-screen entities och en djupare Siri-integration via Apple Intelligence. Om du vill att din app ska kännas som en naturlig del av Apple-ekosystemet är App Intents inte längre något du kan skjuta på — det är en förväntning från användarna.

Så, låt oss dyka in.

Grunderna: Skapa din första App Intent

En App Intent är i grunden en struct som conformar till AppIntent-protokollet. Den definierar en åtgärd din app kan utföra och som systemet kan anropa. Vi börjar med ett enkelt exempel — en intent som lägger till en anteckning:

import AppIntents

struct AddNoteIntent: AppIntent {
    static var title: LocalizedStringResource = "Lägg till anteckning"
    static var description: IntentDescription = "Skapar en ny anteckning i appen"
    
    @Parameter(title: "Innehåll")
    var content: String
    
    @Parameter(title: "Kategori", default: "Allmänt")
    var category: String
    
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let noteManager = NoteManager.shared
        await noteManager.addNote(content: content, category: category)
        return .result(dialog: "Anteckningen har lagts till i \(category)")
    }
}

Här händer några viktiga saker:

  • @Parameter — markerar egenskaper som indata från användaren. Siri kommer automatiskt fråga efter dessa värden om de saknas.
  • perform() — den asynkrona metod som körs när intenten triggas. Returnerar ett IntentResult.
  • ProvidesDialog — gör att Siri kan tala eller visa ett svar till användaren.

Ganska rakt på sak, eller hur?

Parametrar och typer

App Intents stöder ett brett utbud av parametertyper. Förutom grundtyperna som String, Int och Bool kan du använda enums, datum och till och med egna entiteter. Här är ett exempel med en enum för prioritet:

enum NotePriority: String, AppEnum {
    case low = "Låg"
    case medium = "Medium"
    case high = "Hög"
    
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioritet"
    static var caseDisplayRepresentations: [NotePriority: DisplayRepresentation] = [
        .low: "Låg",
        .medium: "Medium",
        .high: "Hög"
    ]
}

struct AddPrioritizedNoteIntent: AppIntent {
    static var title: LocalizedStringResource = "Lägg till prioriterad anteckning"
    
    @Parameter(title: "Innehåll")
    var content: String
    
    @Parameter(title: "Prioritet")
    var priority: NotePriority
    
    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Spara anteckning med prioritet
        return .result(dialog: "Anteckning med prioritet \(priority.rawValue) sparad")
    }
}

Enum-casen dyker upp som fina val i Siri och Genvägar-appen, vilket ger en snygg och typsäker upplevelse.

App Entities: Exponera dina datamodeller

App Entities representerar objekten i din app som systemet behöver förstå. Hanterar din app recept, böcker eller uppgifter? Då blir de modellerna App Entities.

Det här är ärligt talat en av de delar jag tycker är mest elegant i hela ramverket — du beskriver dina data en gång, och sedan kan Siri, Spotlight och Genvägar-appen alla använda dem.

struct NoteEntity: AppEntity {
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Anteckning"
    static var defaultQuery = NoteQuery()
    
    var id: UUID
    var title: String
    var content: String
    var category: String
    var createdAt: Date
    
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(title)",
            subtitle: "\(category)",
            image: .init(systemName: "note.text")
        )
    }
}

struct NoteQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [NoteEntity] {
        let noteManager = NoteManager.shared
        return await noteManager.notes(for: identifiers).map { note in
            NoteEntity(
                id: note.id,
                title: note.title,
                content: note.content,
                category: note.category,
                createdAt: note.createdAt
            )
        }
    }
    
    func suggestedEntities() async throws -> [NoteEntity] {
        let noteManager = NoteManager.shared
        return await noteManager.recentNotes(limit: 10).map { note in
            NoteEntity(
                id: note.id,
                title: note.title,
                content: note.content,
                category: note.category,
                createdAt: note.createdAt
            )
        }
    }
}

EntityQuery är nyckeln som gör att Siri och Spotlight kan hitta och föreslå dina entiteter. Metoden suggestedEntities() returnerar de mest relevanta objekten — tänk på den som ditt fönster mot systemet.

App Shortcuts Provider: Gör din app talbar

För att dina intents ska vara tillgängliga direkt via Siri — utan att användaren manuellt konfigurerar genvägar — behöver du en AppShortcutsProvider. Det är den som kopplar ihop dina intents med naturliga fraser som användaren kan säga.

struct NoteAppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: AddNoteIntent(),
            phrases: [
                "Lägg till en anteckning i \(.applicationName)",
                "Skapa ny anteckning med \(.applicationName)",
                "Skriv ner något i \(.applicationName)"
            ],
            shortTitle: "Ny anteckning",
            systemImageName: "note.text.badge.plus"
        )
        AppShortcut(
            intent: SearchNotesIntent(),
            phrases: [
                "Sök anteckningar i \(.applicationName)",
                "Hitta anteckning i \(.applicationName)"
            ],
            shortTitle: "Sök anteckningar",
            systemImageName: "magnifyingglass"
        )
    }
}

Viktigt att komma ihåg:

  • Varje fras måste innehålla \(.applicationName) så att Siri kopplar kommandot till rätt app.
  • Skriv fraserna som folk faktiskt pratar — naturligt och kort. Ingen säger "vänligen initiera skapandet av en anteckning".
  • App Shortcuts visas automatiskt i Spotlight, Siri-tips och Genvägar-appen utan extra konfiguration.

Spotlight-integration

Sedan iOS 17 kan App Intents exponeras direkt i Spotlight. I iOS 26 har det här fördjupats rejält — Spotlight kan nu trigga dina åtgärder var som helst i systemet.

struct OpenNoteIntent: AppIntent, OpenIntent {
    static var title: LocalizedStringResource = "Öppna anteckning"
    
    @Parameter(title: "Anteckning")
    var target: NoteEntity
    
    func perform() async throws -> some IntentResult {
        // Navigera till anteckningen i appen
        NavigationManager.shared.navigate(to: target.id)
        return .result()
    }
}

Med OpenIntent-protokollet visar Spotlight dina entiteter som sökresultat. Användaren trycker på ett resultat, din intent triggas, och appen navigerar direkt till rätt vy. Smidigt.

SwiftUI-navigation från Spotlight

I iOS 26 kan du koppla ihop Spotlight-resultat med SwiftUI-navigation genom onAppIntentExecution-modifiern:

struct ContentView: View {
    @State private var selectedNoteID: UUID?
    
    var body: some View {
        NavigationStack {
            NoteListView(selectedNoteID: $selectedNoteID)
                .onAppIntentExecution(of: OpenNoteIntent.self) { intent in
                    selectedNoteID = intent.target.id
                }
        }
    }
}

Det fina med det här mönstret är att din app sömlöst navigerar till rätt innehåll oavsett om användaren kommer via Spotlight, Siri eller en genväg. En ingångspunkt, samma beteende.

Nytt i iOS 26: Visual Intelligence

Det här är genuint spännande. I iOS 26 stöder App Intents Visual Intelligence, vilket innebär att din app kan erbjuda resultat direkt i systemets visuella intelligens-upplevelse — till exempel när en användare riktar kameran mot ett objekt eller tar en skärmdump.

struct VisualSearchQuery: IntentValueQuery {
    func query(for descriptor: SemanticContentDescriptor) async throws -> [ProductEntity] {
        let imageAnalyzer = ImageAnalyzer.shared
        let results = await imageAnalyzer.findProducts(matching: descriptor)
        return results.map { product in
            ProductEntity(
                id: product.id,
                name: product.name,
                price: product.price,
                imageURL: product.thumbnailURL
            )
        }
    }
}

Implementeringen bygger på IntentValueQuery-protokollet som tar emot en SemanticContentDescriptor och returnerar en array av App Entities. Systemet visar dina entiteter med deras DisplayRepresentation i sökpanelen, och när användaren trycker på ett resultat triggas motsvarande OpenIntent.

Tänk på möjligheterna: en e-handelsapp som visar produkter när användaren riktar kameran mot en fysisk vara, eller en växtapp som identifierar blommor. Det är den typen av integration som får en app att kännas magisk.

Nytt i iOS 26: Interaktiva Snippets

Snippets har funnits ett tag — det är SwiftUI-vyer som visas i Siri-gränssnittet som bekräftelse eller resultat. Men i iOS 26 kan snippets nu vara interaktiva, och det öppnar helt nya möjligheter.

struct OrderConfirmationIntent: AppIntent {
    static var title: LocalizedStringResource = "Bekräfta beställning"
    
    @Parameter(title: "Beställning")
    var order: OrderEntity
    
    func perform() async throws -> some IntentResult & ShowsSnippetView {
        return .result {
            OrderSnippetView(order: order)
        }
    }
}

struct OrderSnippetView: View {
    let order: OrderEntity
    @State private var confirmed = false
    
    var body: some View {
        VStack(spacing: 12) {
            Text(order.summary)
                .font(.headline)
            Text("Totalt: \(order.total, format: .currency(code: "SEK"))")
                .font(.title2)
            
            Button(intent: ConfirmOrderIntent(orderID: order.id)) {
                Label(
                    confirmed ? "Bekräftad" : "Bekräfta",
                    systemImage: confirmed ? "checkmark.circle.fill" : "cart"
                )
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

Lägg märke till hur knappen använder Button(intent:)-initialisatorn — knapptrycket triggar en annan App Intent direkt från snippet-vyn. Det här är riktigt kraftfullt för arbetsflöden som beställningar, snabba inställningar eller bekräftelser där användaren inte ska behöva öppna appen.

Nytt i iOS 26: On-Screen Entities

Med on-screen entities kan användare interagera med Siri (och ChatGPT) om innehåll som faktiskt visas i din app just nu. Du associerar entiteter med vyer, och systemet gör dem tillgängliga för frågor i kontext.

struct RecipeDetailView: View {
    let recipe: Recipe
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                Text(recipe.title)
                    .font(.largeTitle)
                Text(recipe.description)
                    .font(.body)
                // ... resten av receptvyn
            }
        }
        .onScreenEntity(RecipeEntity(from: recipe))
    }
}

Tänk dig scenariot: en användare tittar på ett recept och frågar Siri "Kan jag byta ut smöret mot kokosolja i det här receptet?" Siri identifierar vad som visas på skärmen och kan skicka kontexten till ChatGPT för ett intelligent svar. Ganska häftigt.

För att det ska fungera måste din entitet conformera till Transferable och stödja relevanta datatyper:

extension RecipeEntity: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        ProxyRepresentation { entity in
            """
            Recept: \(entity.title)
            Ingredienser: \(entity.ingredients.joined(separator: ", "))
            Instruktioner: \(entity.instructions)
            """
        }
    }
}

App Intents i widgetar

App Intents driver interaktiviteten i widgetar sedan iOS 17, och i iOS 26 fungerar det sömlöst ihop med de nya interaktiva snippets. Det bästa? Samma intent kan användas i en widget, i Siri och i Spotlight — du skriver logiken en gång.

struct ToggleTaskWidget: Widget {
    let kind: String = "ToggleTaskWidget"
    
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: TaskTimelineProvider()) { entry in
            TaskWidgetView(entry: entry)
        }
        .configurationDisplayName("Uppgifter")
        .description("Visa och bocka av uppgifter")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

struct TaskWidgetView: View {
    let entry: TaskEntry
    
    var body: some View {
        VStack(alignment: .leading) {
            ForEach(entry.tasks) { task in
                Button(intent: ToggleTaskIntent(taskID: task.id)) {
                    HStack {
                        Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                        Text(task.title)
                            .strikethrough(task.isCompleted)
                    }
                }
            }
        }
        .padding()
    }
}

Genom att återanvända ToggleTaskIntent i widgetar, Siri-genvägar och Spotlight håller du kodbasen ren. Konsekvent beteende oavsett hur användaren väljer att interagera med din app — det är målet.

App Intents vs SiriKit: När ska du använda vad?

Det här är en fråga som dyker upp ofta, så låt oss reda ut det.

  • App Intents — Swift-nativt, koden är källan till sanning, ingen extension krävs, kräver iOS 16+, förstklassigt SwiftUI-stöd, fungerar med Apple Intelligence.
  • SiriKit — äldre mönster (Objective-C/Swift), bygger på .intentdefinition-filer, kräver extension, stöder iOS 10+, begränsat SwiftUI-stöd.

Tumregel: Använd App Intents för all ny utveckling. Punkt. Använd SiriKit bara om du måste stödja iOS-versioner före 16 eller jobbar med specifika SiriKit-domäner som meddelanden eller media som ännu inte migrerats fullt ut till App Intents.

Bästa praxis för App Intents

Efter att ha jobbat en del med App Intents har jag landat i dessa principer som gör störst skillnad:

  1. Gör intents återanvändbara — Samma intent bör kunna användas i Siri, Spotlight, widgetar och genvägar. Duplicera inte logik.
  2. Håll perform() snabb — Intents som tar lång tid ger en usel upplevelse. Om åtgärden kräver att appen öppnas, använd ForegroundContinuableIntent.
  3. Ge tydlig feedback — Använd alltid ProvidesDialog för bekräftelse. Användaren ska aldrig behöva gissa om något funkade.
  4. Testa med riktig Siri — Testa på en riktig enhet med röstkommandon. Simulatorn stöder inte fullständig Siri-interaktion (tyvärr).
  5. Lokalisera fraser — App Shortcut-fraser måste lokaliseras per marknad. Tänk naturligt tal, inte ordagrann översättning.
  6. Begränsa dynamisk data till 4 KB — Använder du App Intents med Live Activities? Kom ihåg att den dynamiska datadelen inte får överstiga 4 KB.

Fullständigt exempelprojekt

Nu sätter vi ihop allt. Här är ett komplett minimalt exempel — en enkel anteckningsapp med Siri-integration som visar hur alla delar hänger ihop:

import SwiftUI
import AppIntents

// MARK: - Datamodell
@Observable
class NoteStore {
    static let shared = NoteStore()
    var notes: [Note] = []
    
    func add(_ text: String, category: String) -> Note {
        let note = Note(id: UUID(), text: text, category: category, createdAt: .now)
        notes.insert(note, at: 0)
        return note
    }
    
    func search(_ query: String) -> [Note] {
        notes.filter { $0.text.localizedCaseInsensitiveContains(query) }
    }
}

struct Note: Identifiable {
    let id: UUID
    let text: String
    let category: String
    let createdAt: Date
}

// MARK: - App Entity
struct NoteEntity: AppEntity {
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Anteckning"
    static var defaultQuery = NoteEntityQuery()
    
    var id: UUID
    var text: String
    var category: String
    
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(text)", subtitle: "\(category)")
    }
}

struct NoteEntityQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [NoteEntity] {
        NoteStore.shared.notes
            .filter { identifiers.contains($0.id) }
            .map { NoteEntity(id: $0.id, text: $0.text, category: $0.category) }
    }
}

// MARK: - Intents
struct CreateNoteIntent: AppIntent {
    static var title: LocalizedStringResource = "Skapa anteckning"
    
    @Parameter(title: "Text")
    var text: String
    
    @Parameter(title: "Kategori", default: "Allmänt")
    var category: String
    
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let note = NoteStore.shared.add(text, category: category)
        return .result(dialog: "Anteckning skapad: \(note.text)")
    }
}

struct SearchNotesIntent: AppIntent {
    static var title: LocalizedStringResource = "Sök anteckningar"
    
    @Parameter(title: "Sökterm")
    var query: String
    
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let results = NoteStore.shared.search(query)
        let count = results.count
        return .result(dialog: "Hittade \(count) anteckning\(count == 1 ? "" : "ar")")
    }
}

// MARK: - App Shortcuts
struct NoteShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: CreateNoteIntent(),
            phrases: [
                "Skapa en anteckning i \(.applicationName)",
                "Ny anteckning med \(.applicationName)"
            ],
            shortTitle: "Ny anteckning",
            systemImageName: "note.text.badge.plus"
        )
        AppShortcut(
            intent: SearchNotesIntent(),
            phrases: [
                "Sök i \(.applicationName)",
                "Hitta anteckning i \(.applicationName)"
            ],
            shortTitle: "Sök",
            systemImageName: "magnifyingglass"
        )
    }
}

// MARK: - SwiftUI App
@main
struct NoteApp: App {
    var body: some Scene {
        WindowGroup {
            NoteListView()
        }
    }
}

Vanliga frågor

Behöver jag ett Apple Developer-konto för att använda App Intents?

Du kan utveckla och testa App Intents i Simulator utan betalkonto. Men för att testa Siri-integration fullt ut på en fysisk enhet behöver du ett Apple Developer-konto med Siri-kapabiliteter aktiverade under Signing & Capabilities i Xcode.

Vad är skillnaden mellan App Intents och SiriKit?

Kort sagt: App Intents är det moderna, Swift-nativa ersättningen. Ingen extension, din Swift-kod är den enda källan till sanning, och ramverket har förstklassigt stöd för SwiftUI och Apple Intelligence. SiriKit behövs fortfarande för domänspecifika integrationer (meddelanden, media) och appar som stöder iOS före 16.

Kan jag använda App Intents med UIKit?

Absolut. App Intents-ramverket är fristående från SwiftUI. Du definierar intents och entiteter i vanlig Swift-kod. Den enda skillnaden är att snippet-vyer (som visas i Siri) kräver SwiftUI, men själva intent-logiken fungerar lika bra med UIKit.

Hur testar jag App Intents under utveckling?

Använd Xcodes inbyggda testverktyg för att köra intents direkt. I iOS 26 finns också Siri Playground i Simulator för grundläggande testning. Men för att verkligen testa röstkommandon och Spotlight-integration rekommenderar jag starkt en fysisk enhet.

Fungerar App Intents med watchOS och macOS?

Ja, alla Apple-plattformar stöds — iOS, macOS, watchOS och tvOS. Från iOS 26 kan intents även leva i Swift Packages och statiska bibliotek, vilket gör det enkelt att dela samma intents mellan plattformar utan att duplicera kod. Det är en stor förbättring för alla som bygger multiplatformsappar.

Om Författaren Daniel Okafor

Daniel is a former Spotify iOS engineer (2019-2024) who worked on the Now Playing surface and the in-app podcast player. He shipped the SwiftUI rewrite of the lyrics view to over 600 million users and contributed several fixes upstream to swift-collections. His writing tends toward the unglamorous corners of iOS work: build-time regressions in Xcode 16, why SwiftData still isn't ready for production sync scenarios, and how to instrument a real app with os_signpost without drowning in noise. He spent two years before Spotify at a fintech startup in Berlin building a banking app on top of Solaris API. Daniel now freelances out of Lisbon and maintains a small open-source library for type-safe deep links in SwiftUI. He has 9 years of native iOS experience.