--- 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]]