App Intents 2.0과 Siri Shortcuts: iOS 27 자동화 가이드
iOS 27을 위한 App Intents 2.0과 Siri Shortcuts 완전 가이드입니다. 스트리밍 응답, 멀티턴 대화, View Annotations 구축, Foundation Models와의 통합을 다룹니다.

App Intents 2.0과 Siri Shortcuts는 iOS 27에서 서드파티 앱이 Siri와 통합하는 유일한 방법입니다. WWDC 2026 이후 Apple은 SiriKit을 비권장으로 지정하고 App Intents를 음성 인터랙션, Spotlight 검색, 자동화 워크플로우의 필수 프레임워크로 자리매김했습니다.
이 글은 iOS 27을 위한 App Intents와 Siri Shortcuts의 완전한 생성 방법을 기본 개념부터 스트리밍 응답, 멀티턴 대화, View Annotations API까지 소개합니다.
App Intents 2.0 프레임워크 이해하기
iOS 16에서 처음 도입되고 WWDC 2026에서 버전 2.0으로 확장된 App Intents 프레임워크는 시스템에서 발견 가능한 액션을 구축하기 위한 Swift 네이티브 선언적 프레임워크입니다. iOS 27에서는 4가지 주요 추가 기능이 있습니다: 장시간 작업을 위한 스트리밍 응답, 멀티턴 대화형 후속 조치, 화면 요소를 참조하기 위한 View Annotations, 학습 구문 없이 시맨틱 이해를 가능하게 하는 App Schemas입니다.
WWDC 2026에서 Apple은 공식적으로 SiriKit을 비권장으로 지정하고 App Intents를 Siri가 서드파티 앱과 상호작용하는 유일한 방법으로 만들었습니다. 기존 SiriKit 코드는 비권장 경고와 함께 컴파일이 계속되지만 Apple은 제거 전 2~3년의 지원 기간을 시사했습니다.
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는 @Parameter 프로퍼티 래퍼를 사용하여 매개변수를 선언하며, Siri가 누락된 값을 대화형으로 요청할 수 있게 합니다. perform() 메서드는 비즈니스 로직을 실행하고 타입이 지정된 결과를 반환합니다. 타입 시스템 자체가 Siri AI가 동적으로 발견하는 스키마 역할을 합니다.
장시간 실행 액션을 위한 스트리밍 응답
App Intents 2.0은 스트리밍 응답을 도입하여 intent가 완료까지 차단하지 않고 실행 중 진행 상황을 보고할 수 있게 합니다. 이 기능은 파일 업로드, 데이터 동기화, 복잡한 계산 등 사용자에게 피드백이 필요한 시나리오를 해결합니다.
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)."
)
}
}스트리밍 응답은 몇 초 이상 걸리는 작업 중에 자연스러운 피드백을 제공하여 사용자를 무음 상태로 대기시키지 않고 참여를 유지합니다.
멀티턴 대화형 후속 조치
멀티턴 대화는 Siri가 단일 세션 내에서 명확화 질문을 하고 상호작용을 계속할 수 있게 합니다. 이 기능은 App Intents를 단발성 명령에서 대화형 워크플로우로 전환합니다.
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."
)
}
}requestValue(for:dialog:) 메서드는 Siri가 사용자의 응답을 받을 때까지 실행을 일시 정지하여 모든 매개변수를 미리 요구하지 않고 자연스러운 주고받기를 생성합니다.
화면 참조를 위한 View Annotations API
View Annotations API는 iOS 27에서 새로 도입되어 사용자가 "이 사진", "세 번째 것", "저 메시지"와 같은 구문으로 Siri 명령에서 UI 요소를 직접 참조할 수 있게 합니다. 이 기능을 사용하려면 SwiftUI 뷰에 시맨틱 정보를 주석으로 추가해야 합니다.
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.")
}
}사용자가 그리드를 보면서 "이 사진 공유해"라고 말하면 Siri는 주석을 통해 "이"를 현재 포커스된 또는 마지막으로 탭한 PhotoEntity로 해석합니다. 이렇게 하면 항목을 말로 설명하는 수고가 줄어듭니다.
View Annotations는 iOS 27이 필요하지만 App Intents 레이어 자체는 iPhone 11 이상을 포함한 모든 iOS 27 기기에서 작동합니다. 화면 인식 같은 Apple Intelligence 기능은 iPhone 15 Pro 이상이 필요합니다.
데이터를 위한 App Entities 정의하기
App Entities는 애플리케이션의 "명사", 즉 intent가 작동하는 객체를 나타냅니다. 이를 통해 Siri는 앱 데이터를 이해하고 검색하고 조작할 수 있습니다. 효과적인 entity 구축은 intent 기반 앱에서 SwiftUI 상태 관리 패턴에 필수적입니다.
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) }
}
}EntityStringQuery 프로토콜은 텍스트 검색 기능을 추가하여 Siri가 이름으로 entity를 찾을 수 있게 합니다. suggestedEntities() 메서드는 Siri와 단축어 인터페이스에 관련 옵션을 제공합니다.
고정된 값 집합을 가진 타입(우선순위, 상태)에는 AppEnum을, 사용자가 생성하는 동적 타입(작업, 메모, 연락처)에는 AppEntity를 사용합니다. 이 둘을 혼합하면 불필요한 복잡성이 발생합니다.
고정 값을 위한 App Enums 만들기
App Enums는 열거형을 시스템에 노출하여 Siri가 시각적 표현과 함께 문맥에 맞는 선택지를 제공할 수 있게 합니다.
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")
)
]
}SF Symbols 아이콘은 단축어 및 Siri 제안에서의 표시를 풍부하게 하여 말로 설명하는 것보다 빠른 선택을 가능하게 합니다.
iOS 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
AppShortcutsProvider 구현하기
AppShortcutsProvider는 App Shortcuts를 시스템에 노출하여 사용자 설정 없이 즉시 사용 가능하게 합니다. 이 단축어들은 Spotlight, Siri, Action Button, 그리고 새로운 Siri AI 에이전트 워크플로우에 나타납니다.
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"
)
}
}음성 구문에는 Siri가 대상 앱을 식별하도록 (.applicationName) 플레이스홀더가 포함되어야 합니다. (.)과 같은 동적 매개변수는 문맥적 명령을 가능하게 합니다.
인터랙티브 위젯에서의 intents
App Intents는 WidgetKit과 통합하여 인터랙티브 위젯을 생성합니다. SwiftUI 내비게이션 패턴을 이해하면 위젯에서 앱으로의 전환 구축에 도움이 됩니다.
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)
}
}위젯은 Button(intent:) 구문을 사용하여 앱을 열지 않고 인터랙션을 App Intent에 직접 연결합니다.
Foundation Models 프레임워크 통합
iOS 27은 온디바이스 LLM 추론을 가능하게 하는 Foundation Models 프레임워크를 도입합니다. Foundation Models는 Siri와 직접 통합되지 않지만 App Intents 내에서 자연어 처리나 응답 생성에 사용할 수 있습니다.
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는 온디바이스에서 초당 약 30토큰의 속도로 작동하며 요청당 비용이 없습니다. 이 프레임워크는 온디바이스 처리를 위해 iPhone 15 Pro 또는 iPhone 16 이상이 필요합니다.
SiriKit에서 App Intents로 마이그레이션
아직 SiriKit을 사용하는 앱의 경우 마이그레이션은 구조화된 프로세스를 따릅니다. Xcode 27은 Widget 구성을 위한 "Convert to App Intent" 도구를 제공하지만 커스텀 핸들러는 수동으로 다시 작성해야 합니다.
// 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))
}
}마이그레이션은 .intentdefinition 파일과 Intent Extension 타겟을 제거합니다. 모든 intent 로직은 Swift 구조체로 메인 앱 타겟으로 이동합니다.
App Intents 테스트하기
AppIntentsTesting 프레임워크는 모킹 없이 실제 Siri, 단축어, Spotlight 인프라를 통해 intents를 검증합니다.
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)
}
}테스트는 시스템 인터페이스와 독립적으로 intent 동작을 검증하여 배포 전 회귀를 포착합니다.
Sources
- Apple WWDC 2026 Announcements : App Intents 2.0 및 SiriKit 비권장
- Swift.org Blog : Swift 6.3 및 6.4 릴리스
- Apple Developer News : iOS 27 SDK 요구 사항 및 Foundation Models 프레임워크
- iOS 27 App Intents Developer Guide : 스트리밍 응답 및 View Annotations 세부 사항
App Intents iOS 27 구현 체크리스트
iOS 27을 대상으로 하는 앱에서 App Intents는 더 이상 선택 사항이 아닙니다. Siri AI는 앱 간 멀티스텝 액션을 구성하며, 공개된 intents가 없는 앱은 이러한 에이전트 워크플로우에서 제외됩니다.
- 앱의 주요 액션을 위한 AppIntents 생성
- 조작 가능한 데이터를 위한 AppEntities 정의
- 열거형에는 AppEnum 사용
- 음성 구문이 포함된 AppShortcutsProvider 구현
- 최대 10개의 App Shortcuts 제한 준수
- 모든 구문에
(.applicationName)포함 - 화면 참조를 위해 SwiftUI 뷰에 View Annotations 추가
- 장시간 실행 작업을 위한 스트리밍 응답 구현
- 복잡한 워크플로우를 위한 멀티턴 대화 지원
- 비권장 기간 종료 전 SiriKit에서 마이그레이션
- AppIntentsTesting 프레임워크로 intents 테스트
- 제목 및 설명 현지화
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
iOS 코드의 버그를 찾을 수 있나요
실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

작성자
Anthony Fillion-MailletSharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 8월 20일 업데이트
태그
공유
관련 기사

WidgetKit iOS 17-26: App Intents로 인터랙티브 위젯 구축하기
WidgetKit과 App Intents로 인터랙티브 iOS 위젯을 만드는 완전한 가이드입니다. 버튼, 토글, 애니메이션, iOS 26 Liquid Glass 머티리얼, WWDC 2026 업데이트를 다룹니다.

Swift에서 Combine vs async/await: 점진적 마이그레이션 패턴
Swift에서 Combine에서 async/await로 마이그레이션하는 완전한 가이드: 점진적 전략, 브리징 패턴, iOS 코드베이스의 패러다임 공존.

2026년 iOS 접근성 면접 질문: VoiceOver와 Dynamic Type
iOS 면접 대비를 위한 핵심 접근성 질문: VoiceOver, Dynamic Type, 시맨틱 traits, 접근성 감사.