App Intents Swift útmutató iOS 26: Siri, Shortcuts és interaktív widgetek (2026)

Teljes App Intents útmutató iOS 26-hoz: építs Siri- és Shortcuts-integrációt, interaktív widgeteket és Focus szűrőket egyetlen Swift kódbázisból, kódpéldákkal és tesztekkel.

App Intents Swift útmutató 2026

Frissítve: 2026. augusztus 7.

Az App Intents az Apple deklaratív keretrendszere, amellyel egyetlen Swift kódbázisból elérhetővé tehetjük az alkalmazás funkcióit a Siri, a Shortcuts, a Spotlight, az interaktív widgetek és a Focus szűrők számára iOS 26, iPadOS 26, macOS 26, watchOS 12 és visionOS 3 alatt. A keretrendszer az AppIntent protokoll köré épül: minden intent egy struct, ami definiálja a paramétereit, a művelet logikáját és a felhasználói interakciót. Az első App Intents integrációmnál (egy jegyzetalkalmazásban) többször újra kellett indítanom az eszközt, mire a Siri felismerte a frázisokat, ezért ebben az útmutatóban azt is végigveszem, milyen apró buktatókra figyelj oda menet közben.

  • Az App Intents egyetlen deklarációval elérhetővé teszi az alkalmazás funkcióit Siri, Shortcuts, Spotlight, widgetek és Focus szűrők számára, nincs külön Intents Extension target.
  • Az AppEntity és EntityQuery segítségével a domain modellek is átadhatók paraméterként, így a felhasználók az alkalmazás konkrét objektumait szerkeszthetik natívan.
  • Az iOS 26 új @ComputedProperty és DynamicOptionsProvider API-kkal bővíti az App Intents-et, valamint az Apple Intelligence integrációval szemantikai keresést tesz lehetővé.
  • Az interaktív widgetek iOS 17 óta App Intents-en alapulnak: egy Button vagy Toggle közvetlenül futtathat egy intentet a widget kontextusában.
  • Cross-platform: ugyanaz az intent Mac Catalyst appban Shortcuts-műveletként, visionOS-ben Focus akcióként, watchOS-en dial complication akcióként jelenhet meg.
  • Az App Intents intentek unit tesztelhetőek, mivel az intent perform() metódusa közvetlenül hívható (nincs szükség UI teszt keretrendszerre).

Mi az App Intents és miért váltotta le a SiriKit-et?

Az App Intents keretrendszer az Apple 2022-es WWDC-jén jelent meg iOS 16-tal, és fokozatosan átvette a helyét a régi SiriKit és Intents Extension architektúrának. A lényegi különbség egyszerű: nincs többé külön extension target, .intentdefinition fájl, generált Objective-C osztály vagy IntentHandler. Egyetlen Swift struct (ami az AppIntent protokollnak felel meg) deklarálja a művelet nevét, paramétereit, összegzését és a végrehajtási logikát. Ez a struct közvetlenül az alkalmazás main targetjében él, és fordítási időben kerül regisztrálásra a rendszernél.

A migrációra jó okok vannak. A régi SiriKit egy zárt domain készlettel dolgozott (Messaging, Payments, VoIP, néhány további), így ha egy alkalmazás funkciója nem illett bele valamelyik intent domain-be, egyszerűen nem lehetett Sirivel elérni. Az App Intents ezt megfordítja: a fejlesztő definiálja a saját domainjét. Egy jegyzetalkalmazás készíthet CreateNoteIntent-et, egy fitness app StartWorkoutIntent-et, egy edzésnaplózó LogSetIntent-et, és mindegyik ugyanolyan első osztályú polgár lesz, mint az Apple beépített intentjei.

Az Apple hivatalos App Intents dokumentációja szerint az iOS 18 óta a SiriKit új appokból nem is fogadható el az App Store review-n; kizárólag régi kód karbantartására használható. Új projektnél tehát nincs mit mérlegelni.

Az első AppIntent megírása

Egy minimális App Intent három részből áll: a title statikus property, ami a Shortcuts appban megjelenik; a description, ami magyarázatot ad; és a perform() aszinkron metódus, ami elvégzi a tényleges munkát. Nézzünk egy egyszerű példát, ami megnyitja az alkalmazás egy adott képernyőjét.

import AppIntents
import SwiftUI

struct OpenTodayViewIntent: AppIntent {
    static var title: LocalizedStringResource = "Mai nap megnyitása"
    static var description = IntentDescription(
        "Megnyitja az alkalmazás Mai nap képernyőjét a friss feladatokkal."
    )

    // Ha az intent futtatásakor az appot előtérbe kell hozni:
    static var openAppWhenRun: Bool = true

    @MainActor
    func perform() async throws -> some IntentResult {
        // A navigáció-vezérlőhöz férünk hozzá, ami a SceneDelegate-ben él
        NavigationCoordinator.shared.push(.todayView)
        return .result()
    }
}

Ez a kód azonnal működik. Fordítás után a Shortcuts appban keresve megjelenik a "Mai nap megnyitása" művelet, és a Siri is felismeri, ha a felhasználó kimondja. Nincs Info.plist bejegyzés, nincs entitlements, nincs extension target, csak a Swift kód. A LocalizedStringResource típus biztosítja, hogy a title automatikusan lokalizálódjon a .xcstrings vagy Localizable.strings fájlokból.

Az @MainActor annotáció itt fontos: az App Intents perform() metódusa alapértelmezésben nem a main threaden fut. Ha UI-t módosítasz vagy főszálhoz kötött state-et frissítesz, explicit módon jelezned kell. A Swift 6.2 Approachable Concurrency segédeszközei (mint a @MainActor következtetés) itt is működnek, és jelentősen csökkentik a boilerplate-et.

Paraméterek, AppEntity és EntityQuery

A valódi erő ott kezdődik, amikor egy intent paramétereket fogad. A paramétereket @Parameter property wrapperrel jelöljük, és a rendszer automatikusan UI-t generál a Shortcuts szerkesztőben. Egyszerű típusok (String, Int, Bool, Date, Duration, EnumType) azonnal működnek.

struct SetTimerIntent: AppIntent {
    static var title: LocalizedStringResource = "Időzítő beállítása"

    @Parameter(title: "Időtartam", default: .init(value: 5, unit: .minutes))
    var duration: Measurement<UnitDuration>

    @Parameter(title: "Címke")
    var label: String?

    static var parameterSummary: some ParameterSummary {
        Summary("Időzítő beállítása \(\.$duration) időre") {
            \.$label
        }
    }

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let seconds = duration.converted(to: .seconds).value
        try await TimerService.shared.start(seconds: seconds, label: label)
        return .result(dialog: "Rendben, \(Int(seconds)) másodperces időzítő elindítva.")
    }
}

A parameterSummary az egyik legfontosabb (de gyakran figyelmen kívül hagyott) része az API-nak. Ez definiálja, hogyan jelenik meg a művelet a Shortcuts szerkesztőben és a Siri visszaigazolásban. A Summary string interpolációval fűzi be a paramétereket, és a nyitó záró blokkban felsorolt paraméterek "opcionális" panelként jelennek meg, így a felhasználó látja a leggyakoribb változtatható értékeket, de nem kell mindet megadnia.

AppEntity: domain modellek intentekbe kötése

Ha az intent az alkalmazás saját objektumaival dolgozik (mondjuk egy jegyzet, egy edzésterv, egy vásárlási lista), akkor AppEntity protokollnak megfelelő típust definiálunk. Az EntityQuery szolgáltatja a kereshető listát a Shortcuts UI számára.

import AppIntents

struct NoteEntity: AppEntity {
    let id: UUID
    let title: String
    let body: String

    static var typeDisplayRepresentation = TypeDisplayRepresentation(
        name: "Jegyzet"
    )

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(title)", subtitle: "\(body.prefix(60))")
    }

    static var defaultQuery = NoteQuery()
}

struct NoteQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [NoteEntity] {
        try await NoteStore.shared.notes(withIds: identifiers)
    }

    func suggestedEntities() async throws -> [NoteEntity] {
        try await NoteStore.shared.recent(limit: 10)
    }
}

struct OpenNoteIntent: AppIntent {
    static var title: LocalizedStringResource = "Jegyzet megnyitása"

    @Parameter(title: "Jegyzet")
    var note: NoteEntity

    static var openAppWhenRun: Bool = true

    @MainActor
    func perform() async throws -> some IntentResult {
        NavigationCoordinator.shared.openNote(id: note.id)
        return .result()
    }
}

Az EntityQuery-nek több változata van: az EntityStringQuery szöveges keresést tesz lehetővé, az EntityPropertyQuery pedig property-alapú szűrést (dátumtartomány, kategória, státusz). A visszaadott NoteEntity-k a Spotlight indexbe is bekerülnek, és a felhasználó direkt a keresésből tud rájuk kattintva navigálni. Ez az a pont, ahol az intent-alapú tervezés hirtelen sokat hoz a konyhára.

AppShortcut és Siri integráció

Ahhoz, hogy a Siri felismerje az intentet konkrét kimondott mondatokra, egy AppShortcutsProvider-t kell regisztrálnunk. Ez a típus deklarálja a triggerkifejezéseket (utterances), az intent kapcsolatát, és opcionálisan azt is, hogy a művelet a Shortcuts appban a Featured szekcióban jelenjen meg.

struct MyAppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: OpenTodayViewIntent(),
            phrases: [
                "Nyisd meg a mai napot \(.applicationName)-ben",
                "\(.applicationName) mai teendők",
                "Mutasd a napi feladatokat a \(.applicationName)-ben"
            ],
            shortTitle: "Mai nap",
            systemImageName: "sun.max.fill"
        )
        AppShortcut(
            intent: SetTimerIntent(),
            phrases: [
                "Indíts időzítőt a \(.applicationName)-ben",
                "\(.applicationName) időzítő"
            ],
            shortTitle: "Időzítő indítása",
            systemImageName: "timer"
        )
    }

    static var shortcutTileColor: ShortcutTileColor = .lightBlue
}

A frázisokat az App Store review-nál is ellenőrzik: legalább egy változatnak tartalmaznia kell az alkalmazás nevét. Ez a rendszer szempontjából egyértelműsítés, hiszen a Siri tudja, melyik appot kell aktiválni. A shortcutTileColor pedig a Shortcuts app "Featured Actions" szekciójában megjelenő csempe színét szabja meg. Az Apple AppShortcutsProvider referenciája részletesen leírja, milyen mondatformulák javasoltak (ige+főnév, illetve app-név+kulcsszó kombinációk).

Interaktív widgetek App Intents-szel

iOS 17-től a WidgetKit gombjai és togglei közvetlenül futtathatnak App Intent-eket a widget kontextusában. Ez az, ami valóban interaktív widgeteket tesz lehetővé, anélkül hogy az app előtérbe kerülne. A widget nézetben:

import SwiftUI
import WidgetKit
import AppIntents

struct ToggleFavoriteIntent: AppIntent {
    static var title: LocalizedStringResource = "Kedvenc állapot váltása"

    @Parameter(title: "Elem azonosító")
    var itemId: String

    init() {}
    init(itemId: String) { self.itemId = itemId }

    func perform() async throws -> some IntentResult {
        try await ItemStore.shared.toggleFavorite(id: itemId)
        return .result()
    }
}

struct FavoritesWidgetView: View {
    let entry: FavoritesEntry

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(entry.item.title)
                .font(.headline)

            Button(intent: ToggleFavoriteIntent(itemId: entry.item.id)) {
                Image(systemName: entry.item.isFavorite ? "star.fill" : "star")
            }
            .buttonStyle(.plain)
            .tint(.yellow)
        }
        .padding()
    }
}

Amikor a felhasználó a widget csillagára koppint, a rendszer meghívja a ToggleFavoriteIntent.perform()-et háttérben, majd automatikusan újratölti a widget időbenyomat-adatait. A widget nem "nyitja meg" az appot; ha ezt szeretnéd elkerülni, az openAppWhenRun-t hagyd az alapértelmezett false értéken (mint fent).

Focus szűrők (Focus Filter Intents)

A Focus szűrők az iOS 16-ban bevezetett funkcionalitás, amivel az alkalmazások alkalmazkodhatnak a felhasználó aktuális Focus módjához (Munka, Alvás, Egyéni). Egy Focus Filter Intent kap egy FocusFilterIntent protokollt, és definiál egy perform() metódust, amit a rendszer akkor hív, amikor a Focus mód aktiválódik vagy deaktiválódik.

struct WorkFocusFilterIntent: SetFocusFilterIntent {
    static var title: LocalizedStringResource = "Munkanézet"
    static var description: LocalizedStringResource = "Munka Focus alatt csak a munkafiókod jegyzeteit mutatja."

    @Parameter(title: "Fiók")
    var account: AccountEntity

    @Parameter(title: "Sötét mód", default: false)
    var forceDarkMode: Bool

    func perform() async throws -> some IntentResult {
        FocusState.shared.applyFilter(accountId: account.id, darkMode: forceDarkMode)
        return .result()
    }
}

Az így definiált szűrő megjelenik a rendszer Focus beállításaiban, és a felhasználó választhat, hogy egy adott Focus módhoz milyen paramétereket rendel. Ez különösen a több fiókos alkalmazásoknál (email kliensek, jegyzetalkalmazások, chat) hasznos, ahol egy Focus mód automatikusan átválthatja a látható fiókot. Őszintén: ez a funkció alábecsült, és sok appban két-három sor kóddal jelentős UX-előnyt lehet vele elérni.

iOS 26 újdonságok: computed properties és Apple Intelligence

Az iOS 26 három jelentős bővítést hozott az App Intents API-ba. Először: a @ComputedProperty lehetővé teszi, hogy egy AppEntity tulajdonságai dinamikusan számítódjanak lekérdezéskor, ne pedig cache-elt értékek legyenek. Ez különösen az élő adatoknál (aktuális készlet, jelenlegi árfolyam) hasznos.

struct StockEntity: AppEntity {
    let id: String
    let symbol: String

    @ComputedProperty
    var currentPrice: Double {
        get async throws {
            try await MarketService.shared.price(for: symbol)
        }
    }

    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Részvény")
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(symbol)")
    }
    static var defaultQuery = StockQuery()
}

Másodszor: az iOS 26 új DynamicOptionsProvider lehetőséget nyújt paraméterek dinamikus listájának előállítására, amelyet a rendszer akkor kér le, amikor a felhasználó éppen kiválasztja az értéket. Ez felváltja a régi statikus enum-alapú módszereket olyan esetekben, ahol az elérhető opciók futásidőben változnak (például csatlakoztatott eszközök listája).

Harmadszor: az Apple Intelligence integrációval a Siri szemantikai keresést végezhet az AppEntity-k között. Az on-device Foundation Models keretrendszer segítségével a rendszer megérti a "tavalyi budapesti utamról szóló jegyzet" típusú kifejezéseket, és megfelelő NoteEntity-t javasol paraméterként, még akkor is, ha a jegyzet címe nem tartalmazza a "budapesti" szót (a lokáció metaadat és a dátum alapján). Ehhez az entitásnak több @Property vagy indexelt mezőt kell exponálnia.

Platformok közötti viselkedés

Ugyanaz az AppIntent struct különböző módokon materializálódik az egyes platformokon. Az alábbi táblázat összefoglalja, hol és hogyan jelenik meg egy intent.

PlatformMegjelenésSpeciális megkötések
iOS 26 / iPadOS 26Siri, Shortcuts, Spotlight, interaktív widgetek, Focus szűrők, Action ButtonWidget kontextusban 30 MB memória limit
macOS 26 (Apple Silicon)Shortcuts.app, Spotlight, Menu Bar ExtrasopenAppWhenRun teljes ablakot nyit, nem csak előtérbe hoz
Mac CatalystUgyanaz mint iPad, de nincs Focus szűrő támogatásWidget hostolás korlátozott
watchOS 12Siri, Smart Stack widget, complications, Double Tap gesztus15 MB memória limit, AppEntity lista max 20 elem
visionOS 3Siri, Shortcuts, Environment scenes akciókImmersive space intentek külön ImmersiveSpaceAppIntent-et igényelnek
tvOS 19Nincs Siri integráció, csak Shortcuts iOS-en hívható tvOS célraCsak AppIntent tesztelésre használatos

Saját tapasztalatból: a legtöbb konfigurációs eltérés a widget kontextusból származik. iPaden egy Home Screen widget kap 30 MB-ot, míg egy watchOS Smart Stack widget csak 15-öt. Ha az intent egy közös ItemStore-hoz nyúl, ami a teljes adathalmazt betölti, ez a watchOS-en OOM-mal fog kilépni, iPaden pedig még belefér. Ilyenkor egy WidgetSyncStore-ot érdemes bevezetni, ami csak a widget-releváns szeletet olvassa be egy dedikált SQLite view-ból.

App Intents tesztelése és hibakeresése

Az App Intents egyik legnagyobb előnye az elődjéhez képest, hogy közvetlenül unit tesztelhető. Nincs mock IntentHandler, nincs XCTest UI runner, a perform() egy közönséges async függvény. A Swift Testing keretrendszerrel egyszerűen írhatunk teszteket.

import Testing
@testable import MyApp

@Suite("SetTimerIntent")
struct SetTimerIntentTests {

    @Test("5 perces időzítő beállítása sikeres")
    func setsFiveMinuteTimer() async throws {
        let intent = SetTimerIntent()
        intent.duration = .init(value: 5, unit: .minutes)
        intent.label = "Kávé"

        let mockService = MockTimerService()
        TimerService.shared = mockService

        let result = try await intent.perform()

        #expect(mockService.lastStartedSeconds == 300)
        #expect(mockService.lastLabel == "Kávé")
    }

    @Test("Negatív időtartam hibát dob")
    func negativeDurationThrows() async throws {
        let intent = SetTimerIntent()
        intent.duration = .init(value: -1, unit: .minutes)

        await #expect(throws: TimerError.self) {
            try await intent.perform()
        }
    }
}

Debuggoláshoz a Console.app-ban a subsystem:com.apple.appintents szűrő megmutatja a rendszer által feldolgozott intenteket, hívási argumentumokat és hibaüzeneteket. Xcode 26-ban új "App Intents Preview" panel is elérhető, ahol a Shortcuts szerkesztő nézete beépítve látható, így nem kell folyamatosan váltogatni az appok között, amikor a parameterSummary-t hangoljuk.

Gyakori hibák és elkerülésük

Az App Intents integráció leggyakoribb hibái ismételhető mintázatokat követnek. Az első: a perform()-ben szinkronban végzett hosszú futású munka. Ha az intent 10 másodperc alatt nem tér vissza, a Siri "Az alkalmazás nem válaszol" üzenettel megszakítja. Ilyenkor bontsd fel a munkát: adj vissza egy ProvidesDialog eredményt gyorsan, és a teljes feldolgozást futtasd háttér Task-ban egy ProcessInfo.expiringActivity-vel megvédve.

Második: az AppEntity-k identitásának instabilitása. Ha az id minden alkalommal újragenerálódik (például UUID() az inicializálóban), a Shortcuts appban elmentett workflow-k eltörnek, mert a rendszer nem találja meg a hivatkozott entitást. Az id mindig legyen perzisztens, például SwiftData vagy Core Data primary key, vagy szerveroldali ID. Ezt a hibát a saját projektemben egy egész délutánig kerestem, mert csak akkor jelentkezett, ha a felhasználó bezárta és újranyitotta a Shortcuts alkalmazást.

Harmadik: @MainActor hiánya UI mutációknál. Swift 6-ban ez már fordítási hibát ad, de Swift 5 mode-ban futásidőben csendes race condition. Ha a projekted még nem Swift 6 concurrency módban fut, kézzel kell figyelned rá.

Negyedik: hiányzó AppShortcutsProvider. Az intent önmagában látszik a Shortcuts appban, de a Siri hívása nem működik, ha nincs regisztrált frázis. Az App Store review néha visszadobja azokat az appokat, amelyekben az App Intents jelen van, de nincs hozzá provider, mert a felhasználó számára "üresnek" tűnik a funkcionalitás.

Ötödik: túl nagy AppEntity objektumok. Ha az entity 100+ KB-os képadatot vagy nagy szöveget hordoz, a rendszer szerializációs limitbe (kb. 4 MB) ütközhet, különösen widget kontextusban. A képeket DisplayRepresentation.Image-en keresztül URL-ként vagy SF Symbol-ként add át, ne base64-ben.

Gyakori kérdések

Mi a különbség az App Intents és a SiriKit között?

Az App Intents deklaratív, Swift-natív, saját domain-t enged definiálni, és nem igényel külön Extension targetet. A SiriKit ezzel szemben egy zárt domain készletre korlátozódik (üzenetküldés, fizetés, VoIP stb.), Objective-C generált kódra épül, és iOS 18 óta új App Store submitokban nem is fogadható el. Új projektnél kizárólag App Intents-et használj.

Hogyan tehetem interaktívvá a widgetemet?

iOS 17-től a widget nézeteiben használhatsz Button(intent:) vagy Toggle(isOn:intent:) komponenseket. A gomb megnyomásakor a hivatkozott AppIntent perform() metódusa fut a háttérben, majd a widget automatikusan újratöltődik. Ne állítsd az openAppWhenRun-t true-ra, ha nem szeretnéd megnyitni az appot minden koppintáskor.

Miért nem ismeri fel a Siri az intentemet?

A leggyakoribb ok, hogy hiányzik egy AppShortcutsProvider, ami regisztrálja a triggerkifejezéseket. A puszta AppIntent deklaráció csak a Shortcuts app számára elérhető, a Siri hangalapú felismeréshez explicit frázisokat kell adnod, és legalább egyben szerepelnie kell az \(.applicationName) placeholdernek. Fordítás után adj a rendszernek 30-60 másodpercet az indexeléshez.

Kell külön intents extension target?

Nem. Ez az egyik legfontosabb eltérés a régi SiriKit architektúrától. Az App Intents kódja közvetlenül a főalkalmazás targetjében él, és a fordító automatikusan regisztrálja őket a rendszernél a build folyamat során. Ha még mindig van egy Intents Extension a projektedben, valószínűleg régi SiriKit-alapú kódot tartalmaz, ez tovább élhet, de új intenteket már a főtargeten belül definiálj.

Az App Intents működik-e watchOS-en?

Igen, watchOS 10 óta teljes körűen támogatott, watchOS 12-től pedig a Double Tap gesztussal is aktiválható. Vedd figyelembe a szigorúbb erőforráskorlátokat: 15 MB memória widget kontextusban, és az AppEntity lista maximum 20 elem lehet a Smart Stack widgetekben. Ha az intent nagy adathalmazon dolgozik, watchOS-specifikus optimalizációra lesz szükséged.

Hiroshi Sato
A Szerzőről Hiroshi Sato

Apple Platforms specialist building for iOS, macOS, visionOS, and the occasional watchOS app nobody asked for.