App Intents 2.0 i Siri Shortcuts: przewodnik automatyzacji iOS 27

Kompletny przewodnik po App Intents 2.0 i Siri Shortcuts dla iOS 27. Tworzenie streaming responses, rozmów wieloturowych, View Annotations i integracja z Foundation Models.

App Intents i Siri Shortcuts dla zaawansowanej automatyzacji iOS ze Swift i Apple Intelligence

App Intents 2.0 i Siri Shortcuts stanowią jedyną ścieżkę integracji aplikacji zewnętrznych z Siri w iOS 27. Po WWDC 2026 Apple wycofało SiriKit i pozycjonowało App Intents jako obowiązkowy framework do interakcji głosowych, wykrywania w Spotlight oraz automatyzacji.

Co obejmuje ten artykuł

Ten artykuł przedstawia kompletne tworzenie App Intents i Siri Shortcuts dla iOS 27, od fundamentalnych koncepcji po streaming responses, rozmowy wieloturowe i View Annotations API.

Zrozumienie frameworku App Intents 2.0

Framework App Intents, wprowadzony w iOS 16 i rozszerzony do wersji 2.0 na WWDC 2026, jest natywnym dla Swift, deklaratywnym frameworkiem do budowania akcji wykrywalnych przez system. iOS 27 wprowadza cztery główne nowości: streaming responses dla długotrwałych operacji, wieloturowe follow-upy konwersacyjne, View Annotations do referencji elementów ekranowych oraz App Schemas dla semantycznego rozumienia bez fraz treningowych.

Na WWDC 2026 Apple formalnie wycofało SiriKit i uczyniło App Intents jedynym sposobem interakcji Siri z aplikacjami zewnętrznymi. Istniejący kod SiriKit nadal kompiluje się z ostrzeżeniami o deprecjacji, ale Apple sygnalizuje dwu- do trzyletnią ramę wsparcia przed całkowitym usunięciem.

TaskIntent.swiftswift
import AppIntents

// An AppIntent represents an action users can perform
struct CreateTaskIntent: AppIntent {
    // Title displayed in Shortcuts and Siri
    static var title: LocalizedStringResource = "Create a task"

    // Description for accessibility and suggestions
    static var description = IntentDescription(
        "Creates a new task in the application."
    )

    // Parameter with automatic validation
    @Parameter(title: "Task title")
    var taskTitle: String

    // Optional parameter with default value
    @Parameter(title: "Priority", default: .medium)
    var priority: TaskPriority

    // Action execution with async/await
    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        // Create task via service
        let task = TaskService.shared.createTask(
            title: taskTitle,
            priority: priority
        )

        // Return created entity for chaining
        return .result(value: TaskEntity(task: task))
    }
}

Intent deklaruje parametry przez property wrapper @Parameter, pozwalając Siri pytać o brakujące wartości w formie konwersacji. Metoda perform() wykonuje logikę biznesową i zwraca typizowany wynik. Sam system typów służy jako schema, którą Siri AI dynamicznie wykrywa.

Streaming Responses dla długotrwałych akcji

App Intents 2.0 wprowadza streaming responses, pozwalające intentom raportować postęp podczas wykonania zamiast blokować do zakończenia. Ta funkcjonalność adresuje scenariusze jak upload plików, synchronizacja danych czy złożone obliczenia wymagające feedbacku dla użytkownika.

StreamingExportIntent.swiftswift
import AppIntents

struct ExportDataIntent: AppIntent {
    static var title: LocalizedStringResource = "Export data"

    @Parameter(title: "Format")
    var format: ExportFormat

    // Streaming response for progress reporting
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let totalItems = DataService.shared.itemCount
        var processed = 0

        // Stream progress updates to Siri
        for item in DataService.shared.allItems {
            await exportItem(item, format: format)
            processed += 1

            // Report progress at intervals
            if processed % 100 == 0 {
                await reportProgress(
                    "Exported (processed) of (totalItems) items..."
                )
            }
        }

        return .result(
            dialog: "Export complete. (totalItems) items saved as (format.rawValue)."
        )
    }
}

Streaming responses umożliwiają naturalny feedback podczas operacji trwających dłużej niż kilka sekund, utrzymując zaangażowanie użytkownika zamiast pozostawiać go w ciszy.

Wieloturowe follow-upy konwersacyjne

Rozmowy wieloturowe pozwalają Siri zadawać pytania wyjaśniające i kontynuować interakcję w ramach jednej sesji. Ta funkcja przekształca App Intents z poleceń jednorazowych w konwersacyjne przepływy pracy.

ConversationalTaskIntent.swiftswift
import AppIntents

struct SmartTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Create smart task"

    @Parameter(title: "Task title")
    var taskTitle: String?

    @Parameter(title: "Priority")
    var priority: TaskPriority?

    @Parameter(title: "Due date")
    var dueDate: Date?

    // Multi-turn conversation flow
    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Request missing parameters conversationally
        let title = try await taskTitle ?? requestValue(
            for: .,
            dialog: "What should the task be called?"
        )

        let taskPriority = try await priority ?? requestValue(
            for: .,
            dialog: "What priority level?"
        )

        // Optional follow-up
        let date: Date?
        if try await requestConfirmation(
            result: .result(dialog: "Should this task have a due date?")
        ) {
            date = try await requestValue(
                for: .,
                dialog: "When is it due?"
            )
        } else {
            date = nil
        }

        let task = TaskService.shared.createTask(
            title: title,
            priority: taskPriority,
            dueDate: date
        )

        return .result(
            dialog: "Created '(task.title)' with (taskPriority.rawValue) priority."
        )
    }
}

Metoda requestValue(for:dialog:) wstrzymuje wykonanie do momentu otrzymania odpowiedzi użytkownika od Siri, tworząc naturalną wymianę zamiast wymagania wszystkich parametrów z góry.

View Annotations API dla referencji ekranowych

View Annotations API, nowość w iOS 27, pozwala użytkownikom odnosić się do elementów UI bezpośrednio w poleceniach Siri używając fraz jak "to zdjęcie", "trzecie" czy "ta wiadomość". Ta funkcjonalność wymaga adnotacji widoków SwiftUI informacjami semantycznymi.

AnnotatedPhotoView.swiftswift
import SwiftUI
import AppIntents

struct PhotoGridView: View {
    let photos: [Photo]

    var body: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
            ForEach(photos) { photo in
                PhotoThumbnail(photo: photo)
                    // Annotate view for Siri reference
                    .appIntentAnnotation(
                        entity: PhotoEntity(photo: photo),
                        label: photo.title
                    )
            }
        }
    }
}

// Intent that accepts view-referenced entities
struct SharePhotoIntent: AppIntent {
    static var title: LocalizedStringResource = "Share photo"

    // Parameter resolved from on-screen annotation
    @Parameter(title: "Photo", supportsViewAnnotation: true)
    var photo: PhotoEntity

    func perform() async throws -> some IntentResult & ProvidesDialog {
        await ShareService.share(photo.id)
        return .result(dialog: "Photo shared.")
    }
}

Kiedy użytkownik mówi "udostępnij to zdjęcie" podczas przeglądania siatki, Siri rozwiązuje "to" na aktualnie sfokusowany lub ostatnio tapnięty PhotoEntity poprzez adnotację. To eliminuje konieczność słownego opisywania elementów.

Kompatybilność urządzeń

View Annotations wymagają iOS 27, ale sama warstwa App Intents działa na każdym urządzeniu z iOS 27, włączając iPhone 11 i nowsze. Funkcje Apple Intelligence jak świadomość ekranu wymagają iPhone 15 Pro lub nowszego.

Definiowanie App Entities dla danych

App Entities reprezentują "rzeczowniki" aplikacji: obiekty, na których operują intenty. Pozwalają Siri rozumieć, wyszukiwać i manipulować danymi aplikacji. Tworzenie efektywnych encji jest kluczowe dla wzorców zarządzania stanem SwiftUI w aplikacjach opartych na intentach.

TaskEntity.swiftswift
import AppIntents

// Internal data model
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool
    var dueDate: Date?
}

// Entity exposed to the system
struct TaskEntity: AppEntity {
    // Required unique identifier
    var id: UUID

    // Displayable properties
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool

    // Display configuration in the system
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Task"

    // Visual representation of the instance
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "(title)",
            subtitle: "(priority.rawValue)",
            image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
        )
    }

    // Default query for searching entities
    static var defaultQuery = TaskEntityQuery()

    // Initializer from internal model
    init(task: Task) {
        self.id = task.id
        self.title = task.title
        self.priority = task.priority
        self.isCompleted = task.isCompleted
    }
}

// Optimized entity query with search
struct TaskEntityQuery: EntityStringQuery {
    // Text search with service-side filtering
    func entities(matching string: String) async throws -> [TaskEntity] {
        TaskService.shared.search(query: string, limit: 10)
            .map { TaskEntity(task: /bin/bash) }
    }

    // Search by identifiers
    func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { identifiers.contains(/bin/bash.id) }
            .map { TaskEntity(task: /bin/bash) }
    }

    // Limited suggestions for performance
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchRecentTasks(limit: 5)
            .map { TaskEntity(task: /bin/bash) }
    }
}

Protokół EntityStringQuery dodaje funkcjonalność wyszukiwania tekstowego, pozwalając Siri znajdować encje po nazwie. Metoda suggestedEntities() zasila interfejsy Siri i Skrótów odpowiednimi opcjami.

AppEnum dla stałych wartości

Należy używać AppEnum dla typów ze stałym zbiorem wartości (priorytet, status), a AppEntity dla dynamicznych typów tworzonych przez użytkownika (zadania, notatki, kontakty). Mieszanie ich powoduje niepotrzebną złożoność.

Tworzenie App Enums dla stałych wartości

App Enums udostępniają systemowi typy wyliczeniowe, pozwalając Siri oferować kontekstowe wybory z wizualnymi reprezentacjami.

TaskPriority.swiftswift
import AppIntents

// Enum exposed to the system
enum TaskPriority: String, AppEnum, Codable {
    case low
    case medium
    case high

    // Type name displayed
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priority"

    // Representation of each case
    static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
        .low: DisplayRepresentation(
            title: "Low",
            image: .init(systemName: "arrow.down.circle")
        ),
        .medium: DisplayRepresentation(
            title: "Medium",
            image: .init(systemName: "minus.circle")
        ),
        .high: DisplayRepresentation(
            title: "High",
            image: .init(systemName: "exclamationmark.circle")
        )
    ]
}

Ikony SF Symbols wzbogacają wyświetlanie w Skrótach i sugestiach Siri, przyspieszając wybór względem opisu słownego.

Gotowy na rozmowy o iOS?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

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, na Action Button oraz w nowych agentowych przepływach Siri AI.

ShortcutsProvider.swiftswift
import AppIntents

// Provider declaring all app shortcuts
struct TaskAppShortcutsProvider: AppShortcutsProvider {
    // Maximum 10 shortcuts per application
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // Shortcut to create a task
        AppShortcut(
            intent: CreateTaskIntent(),
            phrases: [
                // The .applicationName placeholder is REQUIRED
                "Create a task with (.applicationName)",
                "New task in (.applicationName)",
                "Add a task to (.applicationName)"
            ],
            shortTitle: "Create Task",
            systemImageName: "plus.circle"
        )

        // Shortcut with dynamic parameter
        AppShortcut(
            intent: CompleteTaskIntent(),
            phrases: [
                "Complete (.) in (.applicationName)",
                "Mark (.) as done with (.applicationName)"
            ],
            shortTitle: "Complete Task",
            systemImageName: "checkmark.circle"
        )

        // Shortcut for smart conversational intent
        AppShortcut(
            intent: SmartTaskIntent(),
            phrases: [
                "Quick task (.applicationName)",
                "Remind me (.applicationName)"
            ],
            shortTitle: "Smart Task",
            systemImageName: "brain"
        )
    }
}

Frazy głosowe muszą zawierać placeholder (.applicationName), aby Siri zidentyfikowała aplikację docelową. Dynamiczne parametry jak (.) umożliwiają komendy kontekstowe.

Intenty w interaktywnych widgetach

App Intents integrują się z WidgetKit do tworzenia interaktywnych widgetów. Zrozumienie wzorców nawigacji SwiftUI pomaga przy budowaniu przejść widget-aplikacja.

TaskWidgetIntents.swiftswift
import AppIntents
import WidgetKit

// Widget-optimized intent (fast execution)
struct ToggleTaskFromWidgetIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle task"

    @Parameter(title: "Task ID")
    var taskID: String

    init() {}

    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // No dialog for widgets
    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        TaskService.shared.toggleCompletion(taskId: uuid)

        // Immediate widget refresh
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

// Widget view with interactive button
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 połączyć interakcję bezpośrednio z App Intent bez otwierania aplikacji.

Integracja z Foundation Models Framework

iOS 27 wprowadza Foundation Models framework, umożliwiający wnioskowanie LLM na urządzeniu. Choć Foundation Models nie ma bezpośredniej integracji z Siri, aplikacje mogą go używać wewnątrz App Intents do przetwarzania języka naturalnego lub generowania odpowiedzi.

AIAssistedIntent.swiftswift
import AppIntents
import FoundationModels

struct SummarizeNotesIntent: AppIntent {
    static var title: LocalizedStringResource = "Summarize notes"

    @Parameter(title: "Topic")
    var topic: String

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let notes = NoteService.shared.fetchNotes(matching: topic)
        let content = notes.map(.content).joined(separator: "

")

        // Use Foundation Models for on-device summarization
        let model = LanguageModel.default
        let summary = try await model.generate(
            prompt: "Summarize these notes in 2 sentences: (content)",
            maxTokens: 100
        )

        return .result(dialog: IntentDialog(summary))
    }
}

Foundation Models działa z prędkością około 30 tokenów na sekundę na urządzeniu bez żadnych kosztów per zapytanie. Framework wymaga iPhone 15 Pro lub iPhone 16 i nowszych do przetwarzania na urządzeniu.

Migracja z SiriKit do App Intents

Dla aplikacji wciąż używających SiriKit, migracja przebiega według ustalonego procesu. Xcode 27 oferuje narzędzie "Convert to App Intent" dla konfiguracji widgetów, ale niestandardowe handlery wymagają ręcznego przepisania.

swift
// Before: SiriKit Intent Handler
class CreateTaskIntentHandler: NSObject, CreateTaskIntentHandling {
    func handle(intent: CreateTaskIntent) async -> CreateTaskIntentResponse {
        guard let title = intent.taskTitle else {
            return CreateTaskIntentResponse(code: .failure, userActivity: nil)
        }

        let task = TaskService.shared.createTask(title: title)
        return CreateTaskIntentResponse.success(task: task)
    }
}

// After: App Intent (iOS 27)
struct CreateTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Create a task"

    @Parameter(title: "Task title")
    var taskTitle: String

    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        let task = TaskService.shared.createTask(title: taskTitle)
        return .result(value: TaskEntity(task: task))
    }
}

Migracja eliminuje pliki .intentdefinition i targety Intent Extension. Cała logika intentów przenosi się do głównego targetu aplikacji jako struktury Swift.

Testowanie App Intents

Framework AppIntentsTesting waliduje intenty przez rzeczywistą infrastrukturę Siri, Skrótów i Spotlight bez mocków.

TaskIntentTests.swiftswift
import XCTest
import AppIntentsTesting
@testable import TaskApp

final class TaskIntentTests: XCTestCase {

    override func setUp() {
        super.setUp()
        TaskService.shared.reset()
    }

    func testCreateTaskIntent() async throws {
        // Given
        var intent = CreateTaskIntent()
        intent.taskTitle = "Test Task"
        intent.priority = .high

        // When
        let result = try await intent.perform()

        // Then
        let tasks = TaskService.shared.fetchTasks()
        XCTAssertEqual(tasks.count, 1)
        XCTAssertEqual(tasks.first?.title, "Test Task")
        XCTAssertEqual(tasks.first?.priority, .high)
    }

    func testEntityQuery() async throws {
        // Given
        let task1 = TaskService.shared.createTask(title: "Task 1", priority: .low)
        let task2 = TaskService.shared.createTask(title: "Task 2", priority: .high)

        let query = TaskEntityQuery()

        // When
        let entities = try await query.entities(for: [task1.id, task2.id])

        // Then
        XCTAssertEqual(entities.count, 2)
    }

    func testStreamingIntent() async throws {
        // Given
        var intent = ExportDataIntent()
        intent.format = .json

        // When
        let result = try await intent.perform()

        // Then: verify completion dialog
        XCTAssertNotNil(result)
    }
}

Testy weryfikują zachowanie intentów niezależnie od interfejsu systemu, wyłapując regresje przed wdrożeniem.

Źródła

Lista kontrolna implementacji App Intents iOS 27

Dla aplikacji celujących w iOS 27, App Intents nie są już opcjonalne. Siri AI komponuje wielokrokowe akcje między aplikacjami, a aplikacje bez publikowanych intentów są wykluczone z tych agentowych przepływów.

  • Utwórz AppIntents dla głównych akcji aplikacji
  • Zdefiniuj AppEntities dla manipulowalnych danych
  • 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
  • Dodaj View Annotations do widoków SwiftUI dla referencji ekranowych
  • Zaimplementuj streaming responses dla długotrwałych operacji
  • Wspieraj rozmowy wieloturowe dla złożonych przepływów
  • Zmigruj z SiriKit przed zamknięciem okna wsparcia
  • Testuj intenty z frameworkiem AppIntentsTesting
  • Zlokalizuj tytuły i opisy

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Wyzwanie dnia

Znajdziesz błąd w iOS?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 20 sierpnia 2026

Tagi

#app-intents
#siri-shortcuts
#ios
#swift
#apple-intelligence

Udostępnij

Powiązane artykuły