Files
2nd/10_Wiki/Topics/Coding/iOS_App_Intents_Shortcuts.md
T
2026-05-09 21:08:02 +09:00

5.9 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
ios-app-intents-shortcuts App Intents — Shortcuts / Siri / Spotlight Coding draft B conceptual 2026-05-09 2026-05-09
ios
app-intents
siri
shortcuts
vibe-coding
language applicable_to
Swift / AppIntents
iOS
App Intents
Siri
Shortcuts
Spotlight
Focus filter
Apple Intelligence

App Intents (iOS 16+)

Siri / Shortcuts / Spotlight / Focus / Action button 을 한 번에. 선언형 Swift 코드만. iOS 18+ Apple Intelligence 가 자동 사용. 옛 SiriKit / NSUserActivity 보다 단순.

📖 핵심 개념

  • AppIntent: 사용자가 시작 가능한 액션.
  • AppEntity: 도메인 객체 (Note / Item / Recipe).
  • AppShortcutsProvider: 앱 내장 shortcut 등록.
  • Parameter: 사용자에게 물음 또는 자동.

💻 코드 패턴

AppEntity

import AppIntents

struct NoteEntity: AppEntity, Identifiable {
    let id: UUID
    let title: String
    let body: String

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Note"
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(title)")
    }

    static var defaultQuery = NoteQuery()
}

struct NoteQuery: EntityQuery {
    func entities(for ids: [UUID]) async throws -> [NoteEntity] {
        Database.notes(ids: ids)
    }
    func suggestedEntities() async throws -> [NoteEntity] {
        Database.recentNotes(limit: 5)
    }
}

AppIntent

struct CreateNoteIntent: AppIntent {
    static var title: LocalizedStringResource = "Create Note"
    static var description = IntentDescription("Create a new note in the app.")

    @Parameter(title: "Title")
    var title: String

    @Parameter(title: "Body", default: "")
    var body: String

    @MainActor
    func perform() async throws -> some IntentResult & ReturnsValue<NoteEntity> {
        let note = try await Database.create(title: title, body: body)
        return .result(value: note)
    }
}

AppShortcuts

struct AppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: CreateNoteIntent(),
            phrases: [
                "Create note in \(.applicationName)",
                "New \(.applicationName) note",
            ],
            shortTitle: "Create Note",
            systemImageName: "square.and.pencil"
        )
        AppShortcut(
            intent: SearchNotesIntent(),
            phrases: ["Search \(.applicationName)"],
            shortTitle: "Search",
            systemImageName: "magnifyingglass"
        )
    }
}

Open App Intent

struct OpenNoteIntent: AppIntent {
    static var title: LocalizedStringResource = "Open Note"
    static var openAppWhenRun: Bool = true

    @Parameter(title: "Note")
    var note: NoteEntity

    @MainActor
    func perform() async throws -> some IntentResult {
        Router.shared.navigate(to: .note(id: note.id))
        return .result()
    }
}

Spotlight (CoreSpotlight + AppEntity)

import CoreSpotlight

func indexNotes() async {
    let notes = await Database.allNotes()
    let items = notes.map { note -> CSSearchableItem in
        let attrs = CSSearchableItemAttributeSet(contentType: .note)
        attrs.title = note.title
        attrs.contentDescription = note.body
        return CSSearchableItem(uniqueIdentifier: note.id.uuidString, domainIdentifier: "notes", attributeSet: attrs)
    }
    try? await CSSearchableIndex.default().indexSearchableItems(items)
}

// Spotlight tap → handle
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    if userActivity.activityType == CSSearchableItemActionType,
       let id = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {
        Router.shared.navigate(to: .note(id: UUID(uuidString: id)!))
    }
}

Focus filter (집중 모드)

struct WorkFocusFilter: SetFocusFilterIntent {
    static var title: LocalizedStringResource = "Work mode"

    @Parameter(title: "Show only work projects")
    var workOnly: Bool

    func perform() async throws -> some IntentResult {
        FocusManager.shared.setWorkOnly(workOnly)
        return .result()
    }
}

Action button (iPhone 15 Pro+)

  • Settings → Action Button → Shortcut → 앱의 AppShortcut 선택.

Widget interactivity (iOS 17+)

struct CreateNoteButton: View {
    var body: some View {
        Button(intent: CreateNoteIntent(title: "Quick", body: "")) {
            Image(systemName: "plus")
        }
    }
}

위젯에서 직접 intent 실행 — 앱 안 열림.

iOS 18 Apple Intelligence

  • AppEntity + AppIntent 기반으로 Siri 가 똑똑하게 사용.
  • AssistantSchema (예: .system.intents.search) 가 새로움.

🤔 의사결정 기준

기능 사용
Siri / Shortcuts AppIntent + AppShortcutsProvider
Spotlight 검색 CoreSpotlight + AppEntity
Widget 버튼 Button(intent:)
Live Activity 인터랙션 Same intent
Focus mode SetFocusFilterIntent
Action button AppShortcut

안티패턴

  • AppIntent perform 무거움: timeout. 짧게 + background work 따로.
  • Phrases 가 너무 generic: "Search" 같은 거 — 다른 앱과 충돌.
  • localized X: 다국어 사용자 무시. LocalizedStringResource.
  • Open intent + 무 navigation: 앱만 열림 — 사용자 어색.
  • Old SiriKit + AppIntent 둘 다: 혼란. 새로 = AppIntent.
  • Spotlight 인덱스 한 번만: stale. 변경 시 update.
  • EntityQuery suggestedEntities 없음: Shortcut UI 가 아무 거 없음.

🤖 LLM 활용 힌트

  • AppEntity + AppIntent + AppShortcutsProvider 3종.
  • Widget interactivity 도 같은 intent.
  • iOS 18 Apple Intelligence 자동 활용.

🔗 관련 문서