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.
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.
| Platform | Megjelenés | Speciális megkötések |
|---|---|---|
| iOS 26 / iPadOS 26 | Siri, Shortcuts, Spotlight, interaktív widgetek, Focus szűrők, Action Button | Widget kontextusban 30 MB memória limit |
| macOS 26 (Apple Silicon) | Shortcuts.app, Spotlight, Menu Bar Extras | openAppWhenRun teljes ablakot nyit, nem csak előtérbe hoz |
| Mac Catalyst | Ugyanaz mint iPad, de nincs Focus szűrő támogatás | Widget hostolás korlátozott |
| watchOS 12 | Siri, Smart Stack widget, complications, Double Tap gesztus | 15 MB memória limit, AppEntity lista max 20 elem |
| visionOS 3 | Siri, Shortcuts, Environment scenes akciók | Immersive space intentek külön ImmersiveSpaceAppIntent-et igényelnek |
| tvOS 19 | Nincs Siri integráció, csak Shortcuts iOS-en hívható tvOS célra | Csak 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.
