[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
---
|
||||
id: ios-app-intents-shortcuts
|
||||
title: App Intents — Shortcuts / Siri / Spotlight
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [ios, app-intents, siri, shortcuts, vibe-coding]
|
||||
tech_stack: { language: "Swift / AppIntents", applicable_to: ["iOS"] }
|
||||
applied_in: []
|
||||
aliases: [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
|
||||
```swift
|
||||
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
|
||||
```swift
|
||||
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
|
||||
```swift
|
||||
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
|
||||
```swift
|
||||
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)
|
||||
```swift
|
||||
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 (집중 모드)
|
||||
```swift
|
||||
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+)
|
||||
```swift
|
||||
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 자동 활용.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[iOS_Widget_Extension]]
|
||||
- [[iOS_Universal_Links_Deep_Linking]]
|
||||
- [[iOS_Live_Activities]]
|
||||
Reference in New Issue
Block a user