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