4.4 KiB
4.4 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-tipkit-patterns | TipKit — 사용자 onboarding tip | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
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 정의
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
@main
struct MyApp: App {
init() {
try? Tips.configure([
.displayFrequency(.daily),
.datastoreLocation(.applicationDefault),
])
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
popoverTip (anchor)
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)
TipView(WelcomeTip())
.padding()
Rule (parameter)
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)
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)
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 / 빈도
try? Tips.configure([
.displayFrequency(.immediate), // 모든 tip 즉시
// .displayFrequency(.daily) // 하루 1개만
// .displayFrequency(.hourly) // 시간당 1개
])
특정 tip 만 다른 빈도:
struct BigTip: Tip {
var options: [Option] {
Tips.MaxDisplayCount(3) // 최대 3번
Tips.IgnoresDisplayFrequency(true) // 글로벌 무시
}
}
Reset (테스트)
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.