App Intents στο iOS 26: Πλήρης Οδηγός για Siri, Shortcuts και Apple Intelligence
Πλήρης οδηγός για το App Intents framework στο iOS 26: AppIntent, AppEntity, AppShortcut, ενσωμάτωση με Apple Intelligence, Interactive Widgets και migration από SiriKit με κώδικα Swift.
Το App Intents framework είναι το επίσημο API της Apple για iOS 26 με το οποίο εκθέτεις λειτουργίες της εφαρμογής σου στο Siri, στα Shortcuts, στο Spotlight, στα Interactive Widgets και στο Apple Intelligence, με μία και μόνη δήλωση σε Swift. Στο iOS 26 το framework έγινε ο de facto αντικαταστάτης του παλιού SiriKit και είναι το μοναδικό μονοπάτι ώστε η εφαρμογή σου να εμφανίζεται στο νέο πλαίσιο App Intelligence με semantic search και προτεινόμενες ενέργειες ανά πλαίσιο χρήσης.
Το App Intents framework χρησιμοποιεί τα macros @AssistantIntent, @AssistantEntity και @AppShortcutsProvider για declarative ενσωμάτωση με το Apple Intelligence στο iOS 26.
Ένα AppIntent μπορεί να εκτελεστεί από Siri, Shortcuts, Spotlight, Control Center, Action Button και Interactive Widgets χωρίς αλλαγή κώδικα.
Τα AppEntity και EntityQuery εκθέτουν δεδομένα της εφαρμογής σου ώστε το Siri να μπορεί να αναφέρεται σε αυτά με φυσική γλώσσα.
Από το iOS 17.4 και μετά, το SiriKit (Intents.framework) είναι deprecated για custom intents. Πρέπει να μεταναστεύσεις σε App Intents πριν το επόμενο major release.
Τα Interactive Widgets και τα Live Activity buttons στηρίζονται αποκλειστικά σε AppIntent τύπους που είναι @MainActor safe.
Με τα Parameter Summaries και τα IntentDialog μπορείς να ελέγξεις πώς ο χρήστης μιλάει στην εφαρμογή σου μέσω φωνής και πώς εμφανίζεται στα Spotlight previews.
Τι είναι το App Intents framework;
Το App Intents είναι ένα Swift-native framework που η Apple εισήγαγε αρχικά στο iOS 16 και επέκτεινε δραστικά στο iOS 26. Σου επιτρέπει να δηλώσεις ενέργειες (intents) και δεδομένα (entities) της εφαρμογής σου με μία απλή Swift δομή, αντί για τα παλιά .intentdefinition αρχεία του SiriKit. Το system runtime κάνει την ίδια δήλωση διαθέσιμη σε όλα τα επιφάνεια του συστήματος: Siri, Shortcuts.app, Spotlight, Action Button (iPhone 15 Pro και μετά), Control Center, Apple Watch Smart Stack, και (από το iOS 26) στον App Intelligence ranker της Apple.
Στην πράξη, ο τρόπος που δουλεύεις είναι declarative. Δηλώνεις τι κάνει η ενέργεια, ποιες παραμέτρους δέχεται, και τι επιστρέφει. Όλα τα υπόλοιπα (UI για το Shortcuts editor, voice grammar για το Siri, semantic embeddings για το Apple Intelligence) δημιουργούνται αυτόματα από τον compiler μέσω των νέων Swift macros @AssistantIntent και @AssistantEntity. Από την εμπειρία μου που έχω κάνει port apps από SiriKit σε App Intents, το boilerplate πέφτει συνήθως κατά 60–70%. Σοβαρά, η διαφορά είναι αισθητή ήδη από τις πρώτες ώρες.
Σημαντική διαφορά σε σχέση με άλλα Apple frameworks: το App Intents τρέχει εκτός app process σε ένα ειδικό AppIntents Extension όταν καλείται από Siri ή Shortcuts. Αυτό σημαίνει ότι πρέπει να προσέξεις τη διαχείριση state με το @Observable macro και να αποφύγεις τη χρήση singletons που υποθέτουν ότι η εφαρμογή τρέχει στο foreground.
Ο πρώτος σου AppIntent: δομή και παράμετροι
Ένας AppIntent είναι ένα Swift struct που υιοθετεί το πρωτόκολλο AppIntent. Χρειάζεται τρία βασικά στοιχεία: ένα title (πώς θα φαίνεται στο Shortcuts), προαιρετικά μία description, και την μέθοδο perform() που εκτελεί την πραγματική δουλειά. Λοιπόν, δες ένα ολοκληρωμένο παράδειγμα για μια εφαρμογή σημειώσεων.
import AppIntents
import SwiftUI
struct CreateNoteIntent: AppIntent {
static var title: LocalizedStringResource = "Δημιουργία σημείωσης"
static var description = IntentDescription(
"Δημιουργεί μια νέα σημείωση με το κείμενο που έδωσε ο χρήστης."
)
// Παράμετροι που μπορεί να γεμίσει ο χρήστης ή το Siri
@Parameter(title: "Τίτλος", requestValueDialog: "Τι τίτλο να βάλω;")
var noteTitle: String
@Parameter(title: "Περιεχόμενο", inputOptions: .init(multiline: true))
var body: String
// Πώς εμφανίζεται η ενέργεια στο Shortcuts editor
static var parameterSummary: some ParameterSummary {
Summary("Νέα σημείωση \(\.$noteTitle) με κείμενο \(\.$body)")
}
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
let note = try await NoteStore.shared.create(
title: noteTitle,
body: body
)
return .result(
dialog: IntentDialog("Η σημείωση «\(note.title)» αποθηκεύτηκε.")
)
}
}
Δύο πράγματα αξίζουν προσοχή. Πρώτο, το @MainActor attribute είναι σχεδόν πάντα απαραίτητο όταν αγγίζεις SwiftData ή Core Data, γιατί το perform() μπορεί να κληθεί σε background queue από το system. Δεύτερο, το ProvidesDialog result είναι αυτό που κάνει το Siri να «μιλάει» πίσω στον χρήστη. Χωρίς αυτό, η ενέργεια εκτελείται σιωπηλά και ο χρήστης δεν παίρνει επιβεβαίωση φωνής (κάτι που στα πρώτα μου intents το ξέχασα και πέρασε αρκετή ώρα μέχρι να το εντοπίσω).
AppEntity και EntityQuery: εκθέτοντας δεδομένα
Τα intents από μόνα τους είναι ενέργειες χωρίς πλαίσιο. Για να μπορέσει το Siri να καταλάβει φράσεις όπως «Άνοιξε τη σημείωση για το ραντεβού του Νοεμβρίου», χρειάζεσαι το concept του AppEntity. Ένα entity αναπαριστά ένα domain object της εφαρμογής σου (σημείωση, εργασία, παραγγελία, παίκτη) και έχει identifier, display representation, και ένα EntityQuery που ξέρει πώς να φέρει instances.
import AppIntents
struct NoteEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Σημείωση"
static var defaultQuery = NoteQuery()
let id: UUID
let title: String
let snippet: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(snippet)"
)
}
}
struct NoteQuery: EntityQuery {
@MainActor
func entities(for identifiers: [UUID]) async throws -> [NoteEntity] {
try await NoteStore.shared.notes(withIds: identifiers)
.map(NoteEntity.init)
}
@MainActor
func suggestedEntities() async throws -> [NoteEntity] {
// Τι εμφανίζεται στο Shortcuts picker
try await NoteStore.shared.recent(limit: 10).map(NoteEntity.init)
}
}
// String search για το Siri
extension NoteQuery: EntityStringQuery {
@MainActor
func entities(matching string: String) async throws -> [NoteEntity] {
try await NoteStore.shared.search(string).map(NoteEntity.init)
}
}
Το EntityStringQuery είναι αυτό που ξεκλειδώνει το semantic matching. Όταν ο χρήστης πει «Άνοιξε τη σημείωση για το ραντεβού», το Siri καλεί entities(matching:) με τη φράση «ραντεβού» και η εφαρμογή σου επιστρέφει τα πιο σχετικά αποτελέσματα. Αν χρησιμοποιείς SwiftData για αποθήκευση δεδομένων, το mapping από @Model τύπους σε AppEntity είναι σχεδόν μηχανικό και μπορεί να γίνει με ένα generic protocol.
AppShortcut: σύνδεση με Siri και Spotlight
Για να εμφανιστεί ένα intent στο Siri χωρίς ο χρήστης να ανοίξει το Shortcuts.app, πρέπει να το «προωθήσεις» μέσω ενός AppShortcut. Ένα AppShortcut συνδέει το intent με συγκεκριμένες φράσεις φωνής (phrases), ένα system image, και ένα short title.
struct NotesAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: CreateNoteIntent(),
phrases: [
"Δημιούργησε σημείωση στο \(.applicationName)",
"Νέα σημείωση στο \(.applicationName)"
],
shortTitle: "Νέα σημείωση",
systemImageName: "square.and.pencil"
)
AppShortcut(
intent: OpenNoteIntent(),
phrases: [
"Άνοιξε τη σημείωση \(\.$note) στο \(.applicationName)"
],
shortTitle: "Άνοιγμα σημείωσης",
systemImageName: "doc.text"
)
}
static var shortcutTileColor: ShortcutTileColor = .purple
}
Το token \(.applicationName) είναι υποχρεωτικό σε τουλάχιστον μία φράση. Η Apple το απαιτεί ώστε ο χρήστης να ξέρει σε ποιο app μιλάει. Στο iOS 26, μετά την εγκατάσταση της εφαρμογής σου, οι AppShortcuts εμφανίζονται αυτόματα στο Spotlight και στο νέο Siri Suggestions panel χωρίς ο χρήστης να χρειαστεί να κάνει κάτι, αρκεί να έχεις δηλώσει NSAppShortcutsEnabled ως true στο Info.plist.
Ενσωμάτωση με το Apple Intelligence (iOS 26)
Η μεγαλύτερη αλλαγή του 2026 είναι το νέο @AssistantIntent macro. Αντί να γράψεις ξεχωριστό AppIntent για κάθε ενέργεια που θέλεις να εκθέσεις στο Apple Intelligence, χρησιμοποιείς ένα schema που η Apple έχει ήδη ορίσει για κοινούς τομείς (mail, browser, photos, system, files). Το macro παράγει αυτόματα το boilerplate και το συνδέει με τους Apple Intelligence rankers.
import AppIntents
@AssistantIntent(schema: .system.search)
struct SearchNotesIntent: AppIntent {
static var title: LocalizedStringResource = "Αναζήτηση σημειώσεων"
@Parameter(title: "Όρος αναζήτησης")
var criteria: StringSearchCriteria
@MainActor
func perform() async throws -> some IntentResult & ReturnsValue<[NoteEntity]> {
let results = try await NoteStore.shared.search(criteria.term)
return .result(value: results.map(NoteEntity.init))
}
}
@AssistantEntity(schema: .notes.note)
struct AssistantNoteEntity {
let id: UUID
var title: String
var content: String
var creationDate: Date
}
Όταν δηλώνεις schema: .system.search ή schema: .notes.note, η εφαρμογή σου γίνεται eligible να εμφανίζεται όταν ο χρήστης ρωτάει «Βρες τις σημειώσεις μου για το project X» απευθείας στο Siri ή στο νέο Writing Tools popup. Σε αντίθεση με τα κλασικά intents, το Apple Intelligence χρησιμοποιεί on-device embeddings για να αποφασίσει ποια εφαρμογή είναι πιο σχετική, όχι μόνο ποια έχει pattern-matched μία φράση. Αν θέλεις να καταλάβεις πώς δουλεύουν αυτά τα embeddings, ο οδηγός μας για το Foundation Models framework εξηγεί το ίδιο on-device LLM που τα παράγει.
Interactive Widgets και Control Center
Από το iOS 17 τα widgets μπορούν να περιέχουν κουμπιά και toggles, και από το iOS 18 αυτή η ίδια υποδομή χρησιμοποιείται για controls στο Control Center και το Lock Screen. Το κλειδί σε όλα αυτά είναι ένα AppIntent που υιοθετεί τα SetValueIntent ή απλώς εκτελείται από το Button(intent:) initializer του SwiftUI.
struct ToggleFavoriteNoteIntent: AppIntent {
static var title: LocalizedStringResource = "Εναλλαγή αγαπημένου"
static var isDiscoverable = false // Κρυφό από το Shortcuts
@Parameter(title: "Σημείωση")
var note: NoteEntity
@MainActor
func perform() async throws -> some IntentResult {
try await NoteStore.shared.toggleFavorite(id: note.id)
return .result()
}
}
// Μέσα στο widget view
struct NoteWidgetView: View {
let note: NoteEntity
var body: some View {
HStack {
Text(note.title)
Spacer()
Button(intent: ToggleFavoriteNoteIntent(note: note)) {
Image(systemName: note.isFavorite ? "star.fill" : "star")
}
.buttonStyle(.plain)
}
}
}
Όταν ο χρήστης πατήσει το κουμπί, το system εκτελεί το intent εκτός app process, ανανεώνει το widget timeline αυτόματα, και επιστρέφει στον χρήστη χωρίς να ανοίξει την εφαρμογή. Σε visionOS, η ίδια δομή λειτουργεί για ornaments και windowed widgets. Μία από τις πιο όμορφες ιδιότητες του App Intents είναι ότι ο ίδιος intent τύπος δουλεύει χωρίς αλλαγή σε όλες τις πλατφόρμες της Apple, ακόμα και σε Mac Catalyst εφαρμογές που τρέχουν σε iPad-style mode.
App Intents vs SiriKit: τι αλλάζει το 2026
Πολλοί developers με ρωτάνε αν αξίζει να μεταναστεύσουν από SiriKit σε App Intents τώρα ή να περιμένουν. Η σύντομη απάντηση; Ναι, και ήδη είσαι αργοπορημένος. Από το iOS 17.4, τα custom SiriKit intents έχουν χαρακτηριστεί deprecated και η Apple έχει ανακοινώσει ότι θα αφαιρεθούν τελείως σε μελλοντική έκδοση. Δες τη σύγκριση παρακάτω.
Χαρακτηριστικό
App Intents (iOS 16+)
SiriKit (iOS 10–17.3)
Γλώσσα ορισμού
Swift macros & protocols
.intentdefinition XML αρχεία
Compile-time safety
Πλήρης type checking
Generated code, runtime errors
Apple Intelligence support
Ναι (iOS 26)
Όχι
Interactive Widgets
Ναι
Όχι
Control Center controls
Ναι (iOS 18+)
Όχι
Spotlight semantic search
Ναι, μέσω AppEntity
Περιορισμένο
Μέγεθος boilerplate
Χαμηλό
Υψηλό
Status
Active development
Deprecated (17.4+)
Στο πραγματικό migration path, η Apple επιτρέπει να συνυπάρχουν τα δύο frameworks κατά τη διάρκεια της μετάβασης. Μπορείς να μεταφέρεις σταδιακά τα intents ένα-ένα και να ζητήσεις από το system να αντιστοιχίσει τα παλιά NSUserActivity-based shortcuts σε νέα AppIntent instances μέσω του ForwardCompatibleIntent protocol. Σύμφωνα με τις οδηγίες της Apple στο WWDC 2024, η συνύπαρξη υποστηρίζεται μέχρι το iOS 28· μετά δεν υπάρχει εγγύηση.
Best practices και κοινά λάθη
Από την πορεία μου με App Intents σε production apps, υπάρχουν πέντε λάθη που βλέπω επανειλημμένα. Πρώτο, να μη βάζεις heavy logic μέσα στο perform()· αν χρειαστείς πάνω από 5 δευτερόλεπτα, το system σκοτώνει την εκτέλεση. Κάνε offload σε background task και επέστρεψε progress αμέσως. Δεύτερο, μην ξεχάσεις το OpensIntent conformance όταν θες να ανοίξεις την εφαρμογή σου. Χωρίς αυτό η ενέργεια εκτελείται «αόρατα», κάτι που μπορεί να μπερδέψει τους χρήστες (το έχω πάθει σε δικό μου app, και ναι, ήταν μπελάς για debug).
Τρίτο, χρησιμοποίησε IntentParameterDependency για παραμέτρους που εξαρτώνται μεταξύ τους (π.χ. «επίλεξε φάκελο» πριν «επίλεξε σημείωση μέσα στον φάκελο»). Τέταρτο, για localization, τα LocalizedStringResource τύποι παίρνουν automatic strings extraction από το xcstringstool, οπότε δεν χρειάζεσαι Localizable.strings entries χειρωνακτικά. Πέμπτο, πρόσεξε τα @Dependency properties μέσα σε intents· είναι ο σωστός τρόπος να κάνεις dependency injection χωρίς να βασιστείς σε singletons που μπορεί να μην έχουν initialized όταν το intent τρέχει εκτός app process.
Συχνές Ερωτήσεις
Ποια είναι η διαφορά μεταξύ AppIntent και AssistantIntent;
Ο AppIntent είναι ο γενικός τύπος για κάθε ενέργεια που εκθέτει η εφαρμογή σου στο Shortcuts και στο Siri. Ο @AssistantIntent είναι ένα macro που χρησιμοποιεί predefined schemas της Apple (mail, browser, system) ώστε η ενέργεια να γίνεται discoverable απευθείας από το Apple Intelligence χωρίς να χρειάζεται ο χρήστης να ξέρει το όνομα της εφαρμογής σου.
Χρειάζομαι App Intents Extension target;
Όχι, δεν είναι υποχρεωτικό. Από το iOS 16 και μετά μπορείς να δηλώσεις τους intents μέσα στο κύριο app target. Ένα ξεχωριστό extension target έχει νόημα μόνο αν θέλεις να μειώσεις το launch time όταν το Siri ή το widget καλεί τον intent, καθώς το extension μπορεί να φορτώσει πιο γρήγορα από όλη την εφαρμογή.
Πώς κάνω test τα App Intents χωρίς πραγματική συσκευή;
Το Xcode Simulator υποστηρίζει πλήρη εκτέλεση intents από το iOS 17 και μετά. Άνοιξε την εφαρμογή Shortcuts στον simulator, βρες τα AppShortcuts της εφαρμογής σου, και πάτησέ τα. Για Siri voice testing χρειάζεσαι πραγματική συσκευή, αλλά για logic και UI έλεγχο ο simulator καλύπτει το 90% των περιπτώσεων.
Μπορώ να χρησιμοποιήσω App Intents σε watchOS και visionOS;
Ναι. Το App Intents framework υποστηρίζεται σε iOS 16+, iPadOS 16+, macOS 13+, watchOS 9+, visionOS 1+ και tvOS 16+. Ο ίδιος AppIntent τύπος μπορεί να εκτελεστεί σε όλες τις πλατφόρμες χωρίς αλλαγή κώδικα, αρκεί το perform() να μη χρησιμοποιεί APIs που δεν υπάρχουν στη συγκεκριμένη πλατφόρμα (κάνε guard με #available).
Πώς συνδέω App Intents με SwiftData models;
Δημιούργησε ένα AppEntity wrapper γύρω από κάθε @Model τύπο και χρησιμοποίησε @Dependency για να κάνεις inject τον ModelContainer. Στο EntityQuery.entities(for:) κάνεις fetch με FetchDescriptor και mapping σε AppEntity. Πρόσεξε να κάνεις όλα τα ModelContext accesses στον @MainActor, γιατί το App Intents runtime δεν εγγυάται thread context.
Το SensoryFeedback είναι ο declarative τρόπος να παίξεις haptics στο SwiftUI. Οδηγός με όλους τους τύπους, trigger patterns, σύγκριση με Core Haptics και best practices από UX engineer.
Πρακτικός οδηγός για type-safe navigation στο SwiftUI με NavigationStack και NavigationPath: deep linking, pop-to-root, state restoration και accessibility patterns για iOS 26 με έτοιμα παραδείγματα κώδικα.
Το @Observable macro του Swift αντικαθιστά το ObservableObject με ανά-ιδιότητα tracking, λιγότερο boilerplate και αισθητά καλύτερη απόδοση στο SwiftUI.