Apple Intelligence je centralizovaný personálny kontext systému. Keď používateľ povie Siri „pošli zápisník z dnešného mítingu Petrovi“, systém potrebuje vedieť, čo je vo vašej aplikácii „zápisník“, čo je „dnešný míting“ a ako sa „pošle“. A tieto informácie získava jediným spôsobom — cez App Intents. Je to oficiálna (a v podstate jediná) cesta, ktorou sa vaša aplikácia stáva čitateľnou pre Siri, Spotlight, Shortcuts a LLM-based akcie v Shortcuts (nové v iOS 26).
App Intents nie sú len Shortcuts API. Sú to verbs a nouns vašej aplikácie:
- Verbs — akcie typu
AppIntent (otvor poznámku, spusti tréning, pridaj položku na nákupný zoznam).
- Nouns — entity typu
AppEntity (poznámka, tréning, položka).
- Queries — vyhľadávanie entít podľa názvu, identifikátora alebo sémantického dopytu (
EntityQuery, EntityStringQuery, IntentValueQuery).
Keď máte tieto tri stavebné kamene pohromade, systém už dokáže sám vytvárať shortcuts, predikovať akcie, integrovať váš obsah do Visual Intelligence a sprístupniť entity Apple Intelligence cez on-screen entities. To je v podstate magické — a vy v kóde nemusíte pre to nič extra robiť.
Váš prvý App Intent
Začneme jednoduchým intentom, ktorý otvára konkrétnu poznámku. Predpokladajme, že máme typ Note a úložisko NoteStore.
import AppIntents
import SwiftUI
struct OpenNoteIntent: AppIntent {
static let title: LocalizedStringResource = "Otvoriť poznámku"
static let description = IntentDescription(
"Otvorí konkrétnu poznámku v aplikácii.",
categoryName: "Poznámky"
)
static var openAppWhenRun: Bool { true }
@Parameter(title: "Poznámka")
var note: NoteEntity
@Dependency var router: AppRouter
func perform() async throws -> some IntentResult & ProvidesDialog {
await router.open(.note(id: note.id))
return .result(dialog: "Otváram \(note.title).")
}
}
Tri veci, ktoré stoja za pozornosť:
openAppWhenRun hovorí systému, že akcia má vyústiť do otvorenia aplikácie. Pre intenty, čo bežia na pozadí (napr. „Pridaj 1 ks mlieka na nákupný zoznam“), ho nechajte na false.
@Parameter definuje vstup. Systém sa naň pýta používateľa automaticky, ak ho nevie odvodiť z kontextu.
@Dependency (od iOS 18, vylepšené v iOS 26) vám umožní injektovať singletony, routery a stores bez vlastného singleton patternu — registrujete ich v AppDependencyManager.
Registrácia závislostí
import AppIntents
@main
struct NotesApp: App {
init() {
AppDependencyManager.shared.add(dependency: AppRouter.shared)
AppDependencyManager.shared.add(dependency: NoteStore.shared)
}
var body: some Scene {
WindowGroup {
RootView()
}
}
}
App Entities — slovník vašej aplikácie
Entita je doménový objekt, ktorý systém pozná a vie ho odovzdať ako parameter intentu, zobraziť v Shortcuts, použiť v on-screen Siri kontexte alebo vyhľadať cez Spotlight. Stručne: bez entít vaša aplikácia nemá pre systém žiadny slovník.
struct NoteEntity: AppEntity {
static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Poznámka")
static let defaultQuery = NoteQuery()
let id: UUID
let title: String
let preview: String
let updatedAt: Date
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(preview)",
image: .init(systemName: "note.text")
)
}
}
struct NoteQuery: EntityQuery {
@Dependency var store: NoteStore
func entities(for identifiers: [NoteEntity.ID]) async throws -> [NoteEntity] {
await store.notes(matching: identifiers).map(NoteEntity.init)
}
func suggestedEntities() async throws -> [NoteEntity] {
await store.recent(limit: 10).map(NoteEntity.init)
}
}
extension NoteQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [NoteEntity] {
await store.search(query: string).map(NoteEntity.init)
}
}
Ak váš entitný typ konformuje aj k IndexedEntity, systém ho automaticky indexuje pre Spotlight a Visual Intelligence. Žiadny ďalší kód — vaše poznámky sa proste objavia vo vyhľadávaní. Z mojej skúsenosti je to jeden z tých najlacnejších featureov s najlepším pomerom hodnoty k práci.
App Shortcuts — vaše akcie viditeľné okamžite
App Shortcut je „kompilátorovo vyhlásená“ skratka — používateľ ju nemusí pridávať do Shortcuts aplikácie, je dostupná hneď po inštalácii. Zobrazujú sa v Spotlighte, ovládaní Action Buttonu, predikciách Siri a v iOS 26 aj v Visual Intelligence search panel.
struct NotesShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OpenNoteIntent(),
phrases: [
"Otvor poznámku v \(.applicationName)",
"Otvor \(\.$note) v \(.applicationName)"
],
shortTitle: "Otvoriť poznámku",
systemImageName: "note.text"
)
AppShortcut(
intent: NewNoteIntent(),
phrases: [
"Nová poznámka v \(.applicationName)",
"Pridaj poznámku do \(.applicationName)"
],
shortTitle: "Nová poznámka",
systemImageName: "square.and.pencil"
)
}
static let shortcutTileColor: ShortcutTileColor = .grayBlue
}
Pravidlo č. 1: každá fráza musí obsahovať \(.applicationName). Bez toho Siri nevie deterministicky priradiť frázu k vašej aplikácii — a budete sa diviť, prečo akcia nikdy nebeží.
Pravidlo č. 2: v iOS 26 sa odporúča maximálne 10 App Shortcuts. Viac ich systém síce indexuje, ale Spotlight ich už nezobrazuje rovnocenne. Menej je v tomto prípade naozaj viac.
Novinka iOS 26: Interaktívne snippety
Toto je asi najväčšia funkčná zmena v App Intents tento rok — a osobne si myslím, že najpodceňovanejšia. Snippet je SwiftUI view, ktoré váš intent vracia ako svoj výsledok. Používateľ ho vidí v Siri, Shortcuts alebo Spotlighte bez toho, aby otváral vašu aplikáciu. A po novom (iOS 26) môžu byť tieto snippety interaktívne: tlačidlá, refresh, viackrokové formuláre.
Mentálny model snippetov
Snippet view nie je „obyčajný“ SwiftUI view. Pravidlá sú trochu zradné:
- Tlačidlá musia používať iniciálnik
Button(intent:label:). Bežný Button(action:) sa síce vyrenderuje, ale je neinteraktívny.
@State, TextField alebo Toggle s lokálnym binding nie sú interaktívne — text používateľa žiadate cez ďalší AppIntent s parametrami.
- Stav žije medzi intentmi, nie vo view. Namiesto „state machine vo view“ myslite v termínoch chained intents a
@Dependency.
- Metóda
perform() v SnippetIntent-e sa volá opakovane pri každom obnovení. Musí byť idempotentná, lacná a bez side-effectov.
Praktický príklad: interaktívny počítadlo kávy
Predstavme si tracker kávy (môj osobný favorit, lebo kávu pijem nezdravo veľa). Snippet zobrazí dnešný počet a používateľ ho môže priamo upraviť.
import AppIntents
import SwiftUI
@MainActor
final class CoffeeStore {
static let shared = CoffeeStore()
private(set) var todayCount: Int = 2
func increment() { todayCount += 1 }
func decrement() { todayCount = max(0, todayCount - 1) }
}
extension AppDependencyManager {
static func bootstrap() {
AppDependencyManager.shared.add(dependency: CoffeeStore.shared)
}
}
Vstupný intent, ktorý sa vyvolá z fráz Siri alebo z App Shortcut:
struct ShowTodayCoffeeIntent: AppIntent {
static let title: LocalizedStringResource = "Zobraz dnešnú kávu"
static let description = IntentDescription("Otvorí interaktívny snippet s počtom šálok.")
static var openAppWhenRun: Bool { false }
func perform() async throws -> some ShowsSnippetIntent {
.result(snippetIntent: CoffeeSnippetIntent())
}
}
A teraz samotný snippet intent — toto je view-producujúci intent. Volá sa pri každom reload():
struct CoffeeSnippetIntent: SnippetIntent {
static let title: LocalizedStringResource = "Káva — dnes"
@Dependency var store: CoffeeStore
@MainActor
func perform() async throws -> some IntentResult & ShowsSnippetView {
.result(view: CoffeeSnippetView(count: store.todayCount))
}
}
struct CoffeeSnippetView: View {
let count: Int
var body: some View {
VStack(spacing: 12) {
Text("Dnes ste vypili")
.font(.subheadline)
.foregroundStyle(.secondary)
Text("\(count) ☕️")
.font(.system(size: 44, weight: .bold))
.contentTransition(.numericText(value: Double(count)))
HStack(spacing: 24) {
Button(intent: DecrementCoffeeIntent()) {
Image(systemName: "minus.circle.fill")
.font(.title)
}
Button(intent: IncrementCoffeeIntent()) {
Image(systemName: "plus.circle.fill")
.font(.title)
}
}
}
.padding()
}
}
A „mutujúce“ intenty s isDiscoverable = false — tie nemajú zmysel mimo snippetu, takže ich skrývame zo Shortcuts:
struct IncrementCoffeeIntent: AppIntent {
static let title: LocalizedStringResource = "Pridať šálku"
static var isDiscoverable: Bool { false }
@Dependency var store: CoffeeStore
@MainActor
func perform() async throws -> some IntentResult {
store.increment()
await CoffeeSnippetIntent.reload()
return .result()
}
}
struct DecrementCoffeeIntent: AppIntent {
static let title: LocalizedStringResource = "Odobrať šálku"
static var isDiscoverable: Bool { false }
@Dependency var store: CoffeeStore
@MainActor
func perform() async throws -> some IntentResult {
store.decrement()
await CoffeeSnippetIntent.reload()
return .result()
}
}
Statická metóda SnippetIntent.reload() je tu kľúčová. Bez nej by snippet ostal s pôvodným počtom — view sa nepretvára z lokálneho stavu. reload() povie systému, aby globálne refetchol entity-parametre a znovu zavolal perform() na pôvodnom snippet intente. Tento detail mi prvýkrát zabral asi hodinu zbytočného hľadania, takže verte mi: zapamätajte si to.
Viackrokové wizardy s requestConfirmation(actionName:snippetIntent:)
Druhá novinka iOS 26: requestConfirmation(actionName:snippetIntent:) dokáže vnútri bežiaceho intentu zobraziť ďalší snippet a počkať na potvrdenie. Skvelé pre objednávkové toky a všetko, kde chcete „dvojitý súhlas“.
struct OrderCoffeeIntent: AppIntent {
static let title: LocalizedStringResource = "Objednať kávu"
@Parameter(title: "Druh") var kind: CoffeeKindEntity
@Parameter(title: "Veľkosť") var size: CoffeeSize
func perform() async throws -> some IntentResult & ProvidesDialog {
try await requestConfirmation(
actionName: .order,
snippetIntent: OrderSummarySnippetIntent(kind: kind, size: size)
)
let id = try await OrderService.shared.place(kind: kind, size: size)
return .result(dialog: "Objednávka #\(id) je na ceste.")
}
}
Snippet OrderSummarySnippetIntent môže obsahovať tlačidlá pre úpravu (cez ďalšie chained intenty) a nakoniec systém zobrazí natívne potvrdzovacie tlačidlo. Žiadne Apple Pay-style modaly už nie sú potrebné — a to je v zásade veľká úľava.
Visual Intelligence a IntentValueQuery
iOS 26 priniesol Visual Intelligence — používateľ podrží Camera Control alebo otvorí screenshot a systém ponúkne „pozri sa na to v <App>“. Aby sa vaša aplikácia v tomto paneli objavila, potrebujete IntentValueQuery:
struct PlantSpeciesQuery: IntentValueQuery {
func values(for input: SemanticContentDescriptor) async throws -> [PlantSpeciesEntity] {
guard let imageData = input.pixelBuffer.flatMap({ JPEGData(from: $0) }) else {
return []
}
let candidates = try await PlantClassifier.shared.classify(imageData)
return candidates.map(PlantSpeciesEntity.init)
}
}
Tento query systém zavolá s pixel bufferom z fotoaparátu alebo screenshotu. Vaša aplikácia vráti najlepšie zhody ako entity, používateľ ich uvidí ako tile-y v search paneli a po klepnutí sa vyvolá váš OpenIntent.
extension PlantSpeciesEntity: OpenIntent {
@MainActor
func perform() async throws -> some IntentResult {
await AppRouter.shared.open(.species(id: id))
return .result()
}
}
On-screen entities pre Apple Intelligence a ChatGPT
Druhá veľká integrácia. Keď používateľ aktivuje Siri (alebo „Ask ChatGPT“) na obrazovke, kde beží vaša aplikácia, systém potrebuje vedieť, na čo sa to vlastne pozerá. Vy mu to prezradíte cez focusedSceneValue a entity konformné s Transferable:
struct NoteDetailView: View {
let note: Note
var body: some View {
ScrollView {
// ...
}
.focusedSceneValue(\.currentNote, NoteEntity(note))
.userActivity("com.swiftcrafted.notes.viewing", element: note) { activity in
activity.appEntityIdentifier = note.id
activity.title = note.title
}
}
}
extension NoteEntity: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .pdf) { entity in
try await PDFRenderer.render(noteId: entity.id)
}
ProxyRepresentation(exporting: \.title)
}
}
Po tomto môžete v Siri pokojne povedať „pošli toto Petrovi“ a Siri vie, čo „toto“ je — názov, PDF, plain text. Apple Intelligence to aj zhrnie alebo preformuluje, ak treba.
Deferred properties — výkonová novinka iOS 26
Predtým systém pri zobrazovaní výsledku v Shortcuts musel vyhodnotiť všetky properties entity. Pri obrázkoch alebo súboroch to bolo poriadne drahé. iOS 26 teraz prináša @DeferredProperty:
struct PhotoEntity: AppEntity {
let id: UUID
let title: String
@DeferredProperty
var fullImage: IntentFile {
get async throws {
try await PhotoStore.shared.image(for: id)
}
}
}
Property sa vyhodnotí len vtedy, keď ju Shortcuts skutočne potrebuje. Ušetríte sieť, IO aj batériu — a používatelia s veľkými knižnicami fotiek vám budú ticho ďakovať.
Testovanie App Intents
So Swift Testing (Swift 6.2) sa intenty testujú elegantne. Trik je nahradiť závislosti pred spustením perform():
import Testing
@testable import NotesApp
@Suite("OpenNoteIntent")
struct OpenNoteIntentTests {
@Test("Otvorí router so správnym route")
@MainActor
func opensRouter() async throws {
let router = MockRouter()
AppDependencyManager.shared.add(dependency: router)
var intent = OpenNoteIntent()
intent.note = NoteEntity(id: .init(), title: "Test", preview: "", updatedAt: .now)
_ = try await intent.perform()
#expect(router.lastRoute == .note(id: intent.note.id))
}
}
Best practices a časté chyby
- Lokalizujte všetko.
LocalizedStringResource pre title, description, parameterSummary aj frázy App Shortcuts. Siri zlyhá ticho, ak fráza neexistuje v jazyku používateľa.
- parameterSummary je takmer povinný pre dobrú UX v Shortcuts. Bez neho používateľ vidí len suchý zoznam parametrov.
- Žiadne side-effecty v
SnippetIntent.perform(). Volá sa opakovane.
- Vyhnite sa
openAppWhenRun = true, ak to nepotrebujete. Background intent + dialóg + snippet je takmer vždy lepší UX než vyrazenie používateľa z Lock Screenu.
- Indexujte entity cez
IndexedEntity. Spotlight, Visual Intelligence aj Apple Intelligence z toho profitujú zadarmo.
- Limitujte počet App Shortcuts na ~10. Viac začne strácať efektivitu v Spotlighte.
- Predávajte stav cez
@Dependency, nie cez singletony. Ľahšie sa testuje a ľahšie nahrádza v previewoch.
Plán migrácie pre existujúcu aplikáciu
- Identifikujte 3–5 hlavných „verbs“ aplikácie. Začnite tými, ktoré používatelia robia najčastejšie.
- Vytvorte
AppEntity pre 1–2 najdôležitejšie doménové objekty. Konformujte ich k IndexedEntity.
- Pridajte
AppShortcutsProvider s frázami v lokalizačných súboroch.
- Pre kľúčovú akciu, ktorá dáva zmysel ako rýchly náhľad (dnešný stav, najnovšia notifikácia, krátky formulár), pridajte interaktívny snippet.
- Pre obrazovky s detailom doménového objektu nastavte
focusedSceneValue a userActivity na on-screen entity.
- Pridajte
IntentValueQuery, ak vaša aplikácia rozumie obrázkom (rastliny, jedlo, produkty, doklady).
Tento plán pokrýva zhruba 80 % integrácií, ktoré používateľ skutočne vidí, a stojí cca 1–2 sprinty práce. Z mojej skúsenosti je ROI pri prvých dvoch krokoch bezkonkurenčný.
Často kladené otázky
Aký je rozdiel medzi App Intents a SiriKit?
SiriKit pracoval s preddefinovanou množinou domén (správy, jazda, fitness…). App Intents je generický framework — môžete vyjadriť akúkoľvek akciu, akúkoľvek entitu, akýkoľvek dopyt. SiriKit je v iOS 26 pri novom vývoji ne-doporučený; všetka nová integrácia s Apple Intelligence ide cez App Intents.
Potrebujem App Intents, aby som podporoval Shortcuts aplikáciu?
Áno. App Intents je oficiálne API pre Shortcuts od iOS 16. Staré Intent Definition (.intentdefinition) súbory ešte fungujú, ale nemôžu byť použité v App Shortcuts ani v Apple Intelligence — odporúčam migráciu pri najbližšej príležitosti.
Sú interaktívne snippety dostupné aj v iOS 18?
Nie. Interaktívne snippety, SnippetIntent.reload() a requestConfirmation(actionName:snippetIntent:) sú novinky iOS 26. V iOS 17–18 môžete vrátiť statický snippet view ako ShowsSnippetView, ale interaktívne prvky tam jednoducho nepôjdu.
Ako odlíšim App Intent určený pre Apple Intelligence od bežného Shortcuts intentu?
A toto je dobrá správa — nepotrebujete to vôbec odlišovať. Ten istý AppIntent môže bežať z Shortcuts, Spotlightu, Action Buttonu, Siri aj Apple Intelligence. Dôležité je iba: čistý parameterSummary, kvalitný description, lokalizované frázy a entity konformné k IndexedEntity. Apple Intelligence si zvyšok zoradí samo.
Ako fungujú App Intents v App Clips alebo widgetoch?
Widgety (vrátane Live Activities) môžu spúšťať App Intents priamo cez Button(intent:) a Toggle(isOn:intent:) bez otvorenia hostiteľskej aplikácie. App Clips podporujú obmedzenú množinu intentov — typicky tie s openAppWhenRun = false. V oboch prípadoch sú intenty rovnaké, len perform() nesmie závisieť od plnej aplikačnej databázy.
Čo s lokalizáciou fráz pre Slovensko?
Slovenský jazyk pre Siri je v iOS 26 podporovaný cez Apple Intelligence (vyžaduje stiahnutie modelu). App Shortcuts frázy lokalizujte v AppShortcuts.xcstrings a používajte prirodzenú slovenskú konjugáciu — Siri matchuje ľubovoľné gramatické tvary, ak ich máte v stringoch. Aspoň 3 alternatívne frázy na každý intent sú dobrou hygienou.
Záver
App Intents v iOS 26 sú most medzi vašou aplikáciou a celým ekosystémom Apple Intelligence. Začnite jedným intentom a jednou entitou — odmena príde okamžite cez Spotlight a App Shortcuts. Postupne pridajte interaktívne snippety pre rýchlu interakciu, on-screen entities pre Siri a IntentValueQuery pre Visual Intelligence. Výsledkom je aplikácia, ktorá nežije len v ikonke na ploche, ale na každom mieste, kde používateľ niečo robí. A to je presne tá budúcnosť, do ktorej Apple smeruje — bolo by škoda na ňu nenastúpiť.