App Intents i Siri Shortcuts: zaawansowana automatyzacja iOS 2026
Kompletny przewodnik po App Intents i Siri Shortcuts dla iOS 18+. Tworzenie własnych akcji Siri, integracja Apple Intelligence i automatyzacja aplikacji Swift w 2026.

W 2026 roku, dzięki Apple Intelligence i frameworkowi App Intents, aplikacje iOS wkraczają w erę, w której intencja użytkownika jest ważniejsza niż interfejs graficzny. Aplikacje, które nie udostępniają intentów, stają się niewidoczne w systemie zorientowanym na AI. App Intents stanowi fundament umożliwiający Siri, Spotlight, widgetom i Action Button interakcję z funkcjonalnościami aplikacji.
Ten artykuł przedstawia kompletne tworzenie App Intents i Siri Shortcuts dla iOS 18+, od fundamentalnych koncepcji po integrację z Apple Intelligence i App Intent Domains.
Zrozumienie frameworku App Intents
Framework App Intents, wprowadzony wraz z iOS 16, modernizuje konstrukcję intentów w Swift, zastępując starszy framework SiriKit Intents. Ta deklaratywna architektura pozwala tworzyć akcje wykrywalne przez system: Spotlight, aplikacja Skróty, Siri i Action Button.
import AppIntents
// AppIntent reprezentuje akcję, którą użytkownik może wykonać
struct CreateTaskIntent: AppIntent {
// Tytuł wyświetlany w Skrótach i Siri
static var title: LocalizedStringResource = "Utwórz zadanie"
// Opis dla dostępności i sugestii
static var description = IntentDescription(
"Tworzy nowe zadanie w aplikacji."
)
// Parametr z automatyczną walidacją
@Parameter(title: "Tytuł zadania")
var taskTitle: String
// Parametr opcjonalny z wartością domyślną
@Parameter(title: "Priorytet", default: .medium)
var priority: TaskPriority
// Wykonanie akcji
func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
// Utworzenie zadania przez serwis
let task = TaskService.shared.createTask(
title: taskTitle,
priority: priority
)
// Zwraca utworzoną encję do łańcuchowania
return .result(value: TaskEntity(task: task))
}
}Intent deklaruje swoje parametry przez property wrapper @Parameter, pozwalając Siri pytać o brakujące wartości. Metoda perform() wykonuje logikę biznesową i zwraca wynik o określonym typie.
Definiowanie App Entities dla danych
App Entities reprezentują "rzeczowniki" aplikacji: obiekty, na których operują intenty. Pozwalają Siri rozumieć i manipulować danymi aplikacji.
import AppIntents
// Wewnętrzny model danych
struct Task: Identifiable, Codable {
let id: UUID
var title: String
var priority: TaskPriority
var isCompleted: Bool
var dueDate: Date?
}
// Encja wystawiona systemowi
struct TaskEntity: AppEntity {
// Wymagany unikalny identyfikator
var id: UUID
// Wyświetlane właściwości
var title: String
var priority: TaskPriority
var isCompleted: Bool
// Konfiguracja wyświetlania w systemie
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Zadanie"
// Wizualna reprezentacja instancji
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(priority.rawValue)",
image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
)
}
// Domyślne zapytanie do wyszukiwania encji
static var defaultQuery = TaskEntityQuery()
// Initializer z modelu wewnętrznego
init(task: Task) {
self.id = task.id
self.title = task.title
self.priority = task.priority
self.isCompleted = task.isCompleted
}
}
// Zapytanie do wyszukiwania i filtrowania encji
struct TaskEntityQuery: EntityQuery {
// Wyszukiwanie po identyfikatorach
func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { identifiers.contains($0.id) }
.map { TaskEntity(task: $0) }
}
// Sugestie wyświetlane w interfejsie
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { !$0.isCompleted }
.prefix(5)
.map { TaskEntity(task: $0) }
}
}EntityQuery definiuje sposób, w jaki system wyszukuje i sugeruje encje. Metody entities(for:) i suggestedEntities() zasilają interfejsy Siri i Skrótów.
Należy używać AppEnum dla typów ze stałym zbiorem wartości (priorytet, status), a AppEntity dla typów dynamicznych tworzonych przez użytkownika (zadania, notatki, kontakty).
Tworzenie App Enums dla stałych wartości
App Enums udostępniają systemowi typy wyliczeniowe, pozwalając Siri oferować kontekstowe wybory.
import AppIntents
// Enum udostępniony systemowi
enum TaskPriority: String, AppEnum, Codable {
case low
case medium
case high
// Wyświetlana nazwa typu
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priorytet"
// Reprezentacja każdego przypadku
static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
.low: DisplayRepresentation(
title: "Niski",
image: .init(systemName: "arrow.down.circle")
),
.medium: DisplayRepresentation(
title: "Średni",
image: .init(systemName: "minus.circle")
),
.high: DisplayRepresentation(
title: "Wysoki",
image: .init(systemName: "exclamationmark.circle")
)
]
}
// Enum dla statusu zadania
enum TaskStatus: String, AppEnum, Codable {
case pending
case inProgress
case completed
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Status"
static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [
.pending: "Oczekujące",
.inProgress: "W toku",
.completed: "Ukończone"
]
}Reprezentacje wizualne (ikony SF Symbols) wzbogacają wyświetlanie w Skrótach i sugestiach Siri.
Implementacja AppShortcutsProvider
AppShortcutsProvider udostępnia App Shortcuts systemowi, czyniąc je natychmiast dostępnymi bez konfiguracji użytkownika. Te skróty pojawiają się w Spotlight, Siri i Action Button.
import AppIntents
// Provider deklarujący wszystkie skróty aplikacji
struct TaskAppShortcutsProvider: AppShortcutsProvider {
// Maksymalnie 10 skrótów na aplikację
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Skrót do tworzenia zadania
AppShortcut(
intent: CreateTaskIntent(),
phrases: [
// Placeholder .applicationName jest WYMAGANY
"Utwórz zadanie w \(.applicationName)",
"Nowe zadanie w \(.applicationName)",
"Dodaj zadanie do \(.applicationName)"
],
shortTitle: "Utwórz zadanie",
systemImageName: "plus.circle"
)
// Skrót do listowania zadań
AppShortcut(
intent: ListTasksIntent(),
phrases: [
"Pokaż moje zadania w \(.applicationName)",
"Wyświetl zadania z \(.applicationName)",
"Jakie są moje zadania \(.applicationName)"
],
shortTitle: "Moje zadania",
systemImageName: "list.bullet"
)
// Skrót z dynamicznym parametrem
AppShortcut(
intent: CompleteTaskIntent(),
phrases: [
"Ukończ \(\.$taskName) w \(.applicationName)",
"Oznacz \(\.$taskName) jako wykonane w \(.applicationName)"
],
shortTitle: "Ukończ zadanie",
systemImageName: "checkmark.circle"
)
}
}Frazy głosowe muszą zawierać placeholder \(.applicationName), aby Siri zidentyfikowała aplikację docelową. Dynamiczne parametry takie jak \(\.$taskName) umożliwiają komendy kontekstowe.
Aplikacja może zadeklarować maksymalnie 10 App Shortcuts. Warto priorytetyzować akcje najczęstsze i najbardziej użyteczne dla użytkowników.
Intenty z parametrami złożonymi
Intenty mogą przyjmować złożone parametry, w tym encje i opcje konfiguracyjne.
import AppIntents
struct CompleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Ukończ zadanie"
static var description = IntentDescription(
"Oznacza zadanie jako ukończone."
)
// Parametr typu encja z automatycznym wyszukiwaniem
@Parameter(title: "Zadanie")
var task: TaskEntity
// Parametr opcjonalny z datą
@Parameter(title: "Data ukończenia")
var completionDate: Date?
// Konfiguracja dialogu Siri
static var parameterSummary: some ParameterSummary {
Summary("Ukończ \(\.$task)") {
\.$completionDate
}
}
func perform() async throws -> some IntentResult & ProvidesDialog {
// Aktualizacja zadania
TaskService.shared.completeTask(
id: task.id,
completionDate: completionDate ?? Date()
)
// Feedback głosowy dla Siri
return .result(
dialog: "Zadanie \(task.title) zostało oznaczone jako ukończone."
)
}
}
// Intent zwracający listę encji
struct ListTasksIntent: AppIntent {
static var title: LocalizedStringResource = "Wylistuj zadania"
// Filtr opcjonalny
@Parameter(title: "Status", default: nil)
var statusFilter: TaskStatus?
@Parameter(title: "Priorytet", default: nil)
var priorityFilter: TaskPriority?
func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> {
var tasks = TaskService.shared.fetchTasks()
// Zastosuj filtry
if let status = statusFilter {
tasks = tasks.filter {
switch status {
case .completed: return $0.isCompleted
case .pending, .inProgress: return !$0.isCompleted
}
}
}
if let priority = priorityFilter {
tasks = tasks.filter { $0.priority == priority }
}
let entities = tasks.map { TaskEntity(task: $0) }
return .result(value: entities)
}
}ParameterSummary definiuje sposób, w jaki Siri prezentuje intent podczas wykonywania głosowego, dostarczając naturalnego feedbacku.
Gotowy na rozmowy o iOS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Integracja z Apple Intelligence
iOS 18 wprowadza App Intent Domains, kolekcje API zaprojektowane dla konkretnych funkcjonalności. Te domeny pozwalają Apple Intelligence rozumieć i wykonywać akcje z większą precyzją.
import AppIntents
// Zgodność z domeną Bookmarks dla integracji Apple Intelligence
struct SaveBookmarkIntent: AppIntent {
static var title: LocalizedStringResource = "Zapisz zakładkę"
// Parametr URL z automatyczną walidacją
@Parameter(title: "URL")
var url: URL
@Parameter(title: "Tytuł", default: nil)
var title: String?
@Parameter(title: "Folder", default: nil)
var folder: BookmarkFolderEntity?
// Otwórz aplikację w razie potrzeby
static var openAppWhenRun: Bool = false
func perform() async throws -> some IntentResult & ProvidesDialog {
let bookmark = BookmarkService.shared.save(
url: url,
title: title,
folder: folder?.id
)
return .result(
dialog: "Zakładka zapisana: \(bookmark.title)"
)
}
}
// Intent ze świadomością zawartości ekranu (iOS 18.4+)
struct AnalyzeScreenContentIntent: AppIntent {
static var title: LocalizedStringResource = "Analizuj zawartość"
// Dostęp do kontekstu ekranu przez Apple Intelligence
@Parameter(title: "Kontekst")
var screenContext: String?
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let context = screenContext else {
return .result(dialog: "Brak zawartości do analizy.")
}
// Przetwarzaj zawartość wyekstrahowaną przez Apple Intelligence
let analysis = ContentAnalyzer.analyze(context)
return .result(dialog: analysis.summary)
}
}Predefiniowane domeny (Books, Camera, Spreadsheets) pozwalają Siri precyzyjnie odpowiadać na żądania dzięki modelom trenowanym na tych konkretnych zadaniach.
Akcje Siri z potwierdzeniem użytkownika
Dla wrażliwych akcji system może żądać potwierdzenia przed wykonaniem.
import AppIntents
struct DeleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Usuń zadanie"
@Parameter(title: "Zadanie")
var task: TaskEntity
// Wymaga potwierdzenia użytkownika
static var isDiscoverable: Bool = true
func perform() async throws -> some IntentResult & ProvidesDialog {
// Żądanie potwierdzenia przez dialog
try await requestConfirmation(
result: .result(
dialog: "Czy na pewno chcesz usunąć \(task.title)?"
)
)
// Usuń po potwierdzeniu
TaskService.shared.deleteTask(id: task.id)
return .result(
dialog: "Zadanie \(task.title) zostało usunięte."
)
}
}
// Intent z wieloma krokami dialogu
struct ScheduleTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Zaplanuj zadanie"
@Parameter(title: "Zadanie")
var task: TaskEntity
@Parameter(title: "Data")
var scheduledDate: Date
@Parameter(title: "Przypomnienie", default: true)
var setReminder: Bool
static var parameterSummary: some ParameterSummary {
When(\.$setReminder, .equalTo, true) {
Summary("Zaplanuj \(\.$task) na \(\.$scheduledDate) z przypomnieniem")
} otherwise: {
Summary("Zaplanuj \(\.$task) na \(\.$scheduledDate)")
}
}
func perform() async throws -> some IntentResult & ProvidesDialog {
TaskService.shared.schedule(
taskId: task.id,
date: scheduledDate,
reminder: setReminder
)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
let formattedDate = dateFormatter.string(from: scheduledDate)
return .result(
dialog: "Zadanie zaplanowane na \(formattedDate)."
)
}
}Metoda requestConfirmation wstrzymuje wykonanie do walidacji użytkownika, chroniąc przed przypadkowymi akcjami.
Intenty w widgetach interaktywnych
App Intents naturalnie integrują się z WidgetKit do tworzenia interaktywnych widgetów w iOS 17+.
import AppIntents
import WidgetKit
// Intent zoptymalizowany pod widget (szybkie wykonanie)
struct ToggleTaskFromWidgetIntent: AppIntent {
static var title: LocalizedStringResource = "Przełącz zadanie"
@Parameter(title: "ID zadania")
var taskID: String
init() {}
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// Brak dialogu dla widgetów
func perform() async throws -> some IntentResult {
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
TaskService.shared.toggleCompletion(taskId: uuid)
// Natychmiastowe odświeżenie widgetu
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// Widok widgetu z interaktywnym przyciskiem
import SwiftUI
struct TaskWidgetView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskFromWidgetIntent(taskID: task.id)) {
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isCompleted ? .green : .secondary)
Text(task.title)
.strikethrough(task.isCompleted)
}
.padding()
}
.buttonStyle(.plain)
}
}Widgety używają składni Button(intent:), aby bezpośrednio połączyć interakcję z App Intent, bez otwierania aplikacji.
Konfiguracja Action Button
Action Button w iPhone 15 Pro i nowszych może wyzwalać App Shortcuts bezpośrednio.
import AppIntents
// Intent zoptymalizowany pod Action Button
struct QuickCaptureIntent: AppIntent {
static var title: LocalizedStringResource = "Szybkie przechwycenie"
static var description = IntentDescription(
"Szybko tworzy zadanie z tytułem."
)
// Otwiera aplikację do wprowadzenia
static var openAppWhenRun: Bool = true
func perform() async throws -> some IntentResult & OpensIntent {
// Powiadomienie do otwarcia ekranu szybkiego przechwytywania
NotificationCenter.default.post(
name: .quickCaptureTriggered,
object: nil
)
return .result(opensIntent: ShowQuickCaptureViewIntent())
}
}
// Zadeklaruj w AppShortcutsProvider dla Action Button
extension TaskAppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// ... inne skróty
AppShortcut(
intent: QuickCaptureIntent(),
phrases: [
"Szybkie przechwycenie \(.applicationName)",
"Szybka notatka \(.applicationName)"
],
shortTitle: "Przechwyć",
systemImageName: "bolt.circle"
)
}
}Użytkownicy mogą skonfigurować Action Button do wyzwalania tego skrótu przez Ustawienia > Action Button.
iOS 18 wprowadza makra @DeferredProperty i @ComputedProperty redukujące boilerplate. App Intents mogą również znajdować się w Swift Packages dla wieloplatformowego ponownego użycia.
Intenty w Swift Packages
App Intents mogą być definiowane w Swift Packages do współdzielenia między iOS, macOS i watchOS.
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "TaskIntents",
platforms: [
.iOS(.v17),
.macOS(.v14),
.watchOS(.v10)
],
products: [
.library(name: "TaskIntents", targets: ["TaskIntents"])
],
dependencies: [],
targets: [
.target(
name: "TaskIntents",
dependencies: []
)
]
)
// Sources/TaskIntents/SharedIntents.swift
import AppIntents
// Wspólny intent wieloplatformowy
public struct SharedCreateTaskIntent: AppIntent {
public static var title: LocalizedStringResource = "Utwórz zadanie"
@Parameter(title: "Tytuł")
public var taskTitle: String
public init() {}
public func perform() async throws -> some IntentResult {
// Wspólna logika
await TaskRepository.shared.create(title: taskTitle)
return .result()
}
}
// Rozszerzenie specyficzne dla platformy w aplikacji
#if os(iOS)
extension SharedCreateTaskIntent {
// Zachowanie specyficzne dla iOS
static var openAppWhenRun: Bool = false
}
#endifTa architektura utrzymuje pojedynczą bazę kodu intentów, dostosowując zachowanie per platformę.
Testowanie App Intents
App Intents mogą być testowane jednostkowo jak każdy kod Swift.
import XCTest
import AppIntents
@testable import TaskApp
final class TaskIntentTests: XCTestCase {
override func setUp() {
super.setUp()
// Reset serwisu dla testów izolowanych
TaskService.shared.reset()
}
func testCreateTaskIntent() async throws {
// Given
var intent = CreateTaskIntent()
intent.taskTitle = "Zadanie testowe"
intent.priority = .high
// When
let result = try await intent.perform()
// Then
let tasks = TaskService.shared.fetchTasks()
XCTAssertEqual(tasks.count, 1)
XCTAssertEqual(tasks.first?.title, "Zadanie testowe")
XCTAssertEqual(tasks.first?.priority, .high)
}
func testCompleteTaskIntent() async throws {
// Given
let task = TaskService.shared.createTask(
title: "Zadanie do ukończenia",
priority: .medium
)
var intent = CompleteTaskIntent()
intent.task = TaskEntity(task: task)
// When
_ = try await intent.perform()
// Then
let updatedTask = TaskService.shared.fetchTask(id: task.id)
XCTAssertTrue(updatedTask?.isCompleted ?? false)
}
func testEntityQuery() async throws {
// Given
let task1 = TaskService.shared.createTask(title: "Zadanie 1", priority: .low)
let task2 = TaskService.shared.createTask(title: "Zadanie 2", priority: .high)
let query = TaskEntityQuery()
// When
let entities = try await query.entities(for: [task1.id, task2.id])
// Then
XCTAssertEqual(entities.count, 2)
}
}Testy weryfikują zachowanie intentów niezależnie od interfejsu systemu.
Najlepsze praktyki i optymalizacje
Kilka wzorców zapewnia wydajne App Intents i optymalne doświadczenie użytkownika.
import AppIntents
// 1. Frazy zlokalizowane
struct LocalizedTaskIntent: AppIntent {
static var title: LocalizedStringResource = "task.create.title"
static var description = IntentDescription(
"task.create.description",
categoryName: "task.category"
)
@Parameter(title: "task.parameter.title")
var taskTitle: String
func perform() async throws -> some IntentResult {
// ...
return .result()
}
}
// 2. Prawidłowa obsługa błędów
enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible {
case taskNotFound
case invalidInput
case serviceUnavailable
var localizedStringResource: LocalizedStringResource {
switch self {
case .taskNotFound:
return "Nie znaleziono zadania."
case .invalidInput:
return "Nieprawidłowe dane."
case .serviceUnavailable:
return "Usługa tymczasowo niedostępna."
}
}
}
struct RobustTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Solidny intent"
@Parameter(title: "ID")
var taskId: String
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let uuid = UUID(uuidString: taskId) else {
throw TaskIntentError.invalidInput
}
guard let task = TaskService.shared.fetchTask(id: uuid) else {
throw TaskIntentError.taskNotFound
}
return .result(dialog: "Zadanie znalezione: \(task.title)")
}
}
// 3. Optymalizacja EntityQuery
struct OptimizedTaskQuery: EntityStringQuery {
// Zoptymalizowane wyszukiwanie tekstowe
func entities(matching string: String) async throws -> [TaskEntity] {
// Wyszukiwanie po stronie serwisu z limitem
TaskService.shared.search(query: string, limit: 10)
.map { TaskEntity(task: $0) }
}
// Ograniczone sugestie dla wydajności
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchRecentTasks(limit: 5)
.map { TaskEntity(task: $0) }
}
}
// 4. Focus Filter dla trybów Skupienia
struct TaskFocusFilter: SetFocusFilterIntent {
static var title: LocalizedStringResource = "Filtruj zadania"
@Parameter(title: "Pokaż tylko wysoki priorytet")
var showHighPriorityOnly: Bool
func perform() async throws -> some IntentResult {
TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly)
return .result()
}
}Te wzorce zapewniają płynną integrację z systemem przy zachowaniu optymalnej wydajności.
Podsumowanie
App Intents i Siri Shortcuts transformują sposób, w jaki użytkownicy wchodzą w interakcję z aplikacjami iOS. W 2026 roku, dzięki Apple Intelligence, ujawnianie intentów nie jest już opcjonalne, ale niezbędne do dostarczania nowoczesnego i intuicyjnego doświadczenia.
Lista kontrolna App Intents iOS 18+
- ✅ Utwórz AppIntents dla głównych akcji aplikacji
- ✅ Zdefiniuj AppEntities dla danych do manipulacji
- ✅ Użyj AppEnum dla typów wyliczeniowych
- ✅ Zaimplementuj AppShortcutsProvider z frazami głosowymi
- ✅ Przestrzegaj limitu maksymalnie 10 App Shortcuts
- ✅ Uwzględnij
\(.applicationName)we wszystkich frazach - ✅ Skonfiguruj ParameterSummary dla feedbacku Siri
- ✅ Zintegruj domeny Apple Intelligence, jeśli dotyczy
- ✅ Testuj intenty testami jednostkowymi
- ✅ Zlokalizuj tytuły i opisy
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

WidgetKit iOS 17+: Interaktywne Widgety z App Intents
Kompletny przewodnik tworzenia interaktywnych widgetów iOS z WidgetKit i App Intents. Przyciski, przełączniki, animacje i najlepsze praktyki dla iOS 17+ w 2026 roku.

Combine vs async/await w Swift: Wzorce Progresywnej Migracji
Kompletny przewodnik po migracji z Combine do async/await w Swift: progresywne strategie, wzorce mostkowania i współistnienie paradygmatów w bazach kodu iOS.

Pytania rekrutacyjne o dostępność iOS w 2026: VoiceOver i Dynamic Type
Przygotuj się do rozmów iOS z kluczowymi pytaniami o dostępność: VoiceOver, Dynamic Type, semantyczne traits i audyty.