[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
---
|
||||
id: ios-tipkit-patterns
|
||||
title: TipKit — 사용자 onboarding tip
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [ios, tipkit, onboarding, vibe-coding]
|
||||
tech_stack: { language: "Swift / SwiftUI", applicable_to: ["iOS"] }
|
||||
applied_in: []
|
||||
aliases: [TipKit, popoverTip, TipView, hint, contextual help]
|
||||
---
|
||||
|
||||
# TipKit (iOS 17+)
|
||||
|
||||
> Apple 공식 onboarding / hint 프레임워크. **선언형 + rule-based**. 한 번 본 tip 자동 추적, 빈도 제한, A/B 가능. 이전 자체 popup 보다 깔끔.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- Tip protocol: title + message + image + actions.
|
||||
- Rule: 표시 조건 (parameter / event).
|
||||
- popoverTip / TipView: 표시 위치.
|
||||
- 자동 dismiss / 빈도 제한.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### Tip 정의
|
||||
```swift
|
||||
import TipKit
|
||||
|
||||
struct FavoriteTip: Tip {
|
||||
var title: Text { Text("Save your favorites") }
|
||||
var message: Text? { Text("Tap the heart to save items.") }
|
||||
var image: Image? { Image(systemName: "heart") }
|
||||
}
|
||||
```
|
||||
|
||||
### App startup configure
|
||||
```swift
|
||||
@main
|
||||
struct MyApp: App {
|
||||
init() {
|
||||
try? Tips.configure([
|
||||
.displayFrequency(.daily),
|
||||
.datastoreLocation(.applicationDefault),
|
||||
])
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup { ContentView() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### popoverTip (anchor)
|
||||
```swift
|
||||
struct ItemRow: View {
|
||||
let tip = FavoriteTip()
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("Item")
|
||||
Spacer()
|
||||
Image(systemName: "heart")
|
||||
.popoverTip(tip)
|
||||
.onTapGesture {
|
||||
tip.invalidate(reason: .actionPerformed)
|
||||
// 사용했으니 다시 안 띄움
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TipView (inline)
|
||||
```swift
|
||||
TipView(WelcomeTip())
|
||||
.padding()
|
||||
```
|
||||
|
||||
### Rule (parameter)
|
||||
```swift
|
||||
struct SearchTip: Tip {
|
||||
@Parameter static var hasSearched: Bool = false
|
||||
|
||||
var rules: [Rule] {
|
||||
#Rule(SearchTip.$hasSearched) { $0 == false }
|
||||
// 사용자가 한 번도 search 안 했을 때만
|
||||
}
|
||||
|
||||
var title: Text { Text("Try search") }
|
||||
}
|
||||
|
||||
// 검색 발생 시
|
||||
SearchTip.hasSearched = true
|
||||
```
|
||||
|
||||
### Rule (event)
|
||||
```swift
|
||||
struct LongTapTip: Tip {
|
||||
static let didLongTap = Event(id: "didLongTap")
|
||||
|
||||
var rules: [Rule] {
|
||||
#Rule(Self.didLongTap) { $0.donations.count >= 3 }
|
||||
// 3번 길게 탭한 후 표시
|
||||
}
|
||||
|
||||
var title: Text { Text("Try Quick Action") }
|
||||
var message: Text? { Text("Long press for shortcuts") }
|
||||
}
|
||||
|
||||
// 탭 발생 시
|
||||
Task { await LongTapTip.didLongTap.donate() }
|
||||
```
|
||||
|
||||
### Action (CTA)
|
||||
```swift
|
||||
struct UpgradeTip: Tip {
|
||||
var actions: [Action] {
|
||||
Action(id: "upgrade", title: "Upgrade")
|
||||
Action(id: "later", title: "Later")
|
||||
}
|
||||
|
||||
var title: Text { Text("Go Pro") }
|
||||
}
|
||||
|
||||
// 처리
|
||||
.popoverTip(tip) { action in
|
||||
switch action.id {
|
||||
case "upgrade": showUpgrade = true
|
||||
case "later": tip.invalidate(reason: .actionPerformed)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frequency / 빈도
|
||||
```swift
|
||||
try? Tips.configure([
|
||||
.displayFrequency(.immediate), // 모든 tip 즉시
|
||||
// .displayFrequency(.daily) // 하루 1개만
|
||||
// .displayFrequency(.hourly) // 시간당 1개
|
||||
])
|
||||
```
|
||||
|
||||
특정 tip 만 다른 빈도:
|
||||
```swift
|
||||
struct BigTip: Tip {
|
||||
var options: [Option] {
|
||||
Tips.MaxDisplayCount(3) // 최대 3번
|
||||
Tips.IgnoresDisplayFrequency(true) // 글로벌 무시
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Reset (테스트)
|
||||
```swift
|
||||
try? Tips.resetDatastore()
|
||||
FavoriteTip().invalidate(reason: .actionPerformed)
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 상황 | 추천 |
|
||||
|---|---|
|
||||
| iOS 17+ | TipKit |
|
||||
| 다양한 OS | 자체 popover + UserDefaults |
|
||||
| 강력 onboarding (3-step) | OnboardingView 직접 |
|
||||
| Contextual hint | TipKit popoverTip |
|
||||
| Marketing modal | App Store 권장 X (자체 sheet) |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **TipKit configure 안 함**: tip 안 보임.
|
||||
- **Invalidate 누락**: 영원 보임.
|
||||
- **Tip 너무 많음**: 사용자 피로. priority + frequency.
|
||||
- **Tip 이 광고**: Apple 가이드 위반. 진짜 hint 만.
|
||||
- **Modal 같은 거대 tip**: 작은 popover 만.
|
||||
- **Reset 없음 (dev)**: 디버깅 시 다시 못 봄.
|
||||
- **Rule 복잡**: 디버깅 어려움. 단순.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- iOS 17+ = TipKit.
|
||||
- Tip + Rule (parameter / event) + Frequency 3종.
|
||||
- Reset 으로 dev cycle.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[iOS_SwiftUI_State_Property_Wrappers]]
|
||||
- [[iOS_App_Clips]]
|
||||
- [[iOS_Live_Activities]]
|
||||
Reference in New Issue
Block a user