Skip to content

Latest commit

ย 

History

History
399 lines (323 loc) ยท 9.13 KB

File metadata and controls

399 lines (323 loc) ยท 9.13 KB

TipKit AI Reference

๊ธฐ๋Šฅ ํŒ ๋ฐ ์˜จ๋ณด๋”ฉ ๊ฐ€์ด๋“œ. ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ณ  TipKit ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐœ์š”

TipKit์€ ์•ฑ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ ์‹œ์ ์— ์•ˆ๋‚ดํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ํŒ ํ‘œ์‹œ ์กฐ๊ฑด, ๋นˆ๋„, ์šฐ์„ ์ˆœ์œ„๋ฅผ ์‹œ์Šคํ…œ์ด ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

ํ•„์ˆ˜ Import

import TipKit

์•ฑ ์„ค์ •

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    try? Tips.configure([
                        .displayFrequency(.immediate),  // ๋˜๋Š” .daily, .weekly, .monthly
                        .datastoreLocation(.applicationDefault)
                    ])
                }
        }
    }
}

ํ•ต์‹ฌ ๊ตฌ์„ฑ์š”์†Œ

1. ๊ธฐ๋ณธ ํŒ ์ •์˜

struct FavoriteTip: Tip {
    var title: Text {
        Text("์ฆ๊ฒจ์ฐพ๊ธฐ ์ถ”๊ฐ€")
    }
    
    var message: Text? {
        Text("ํ•˜ํŠธ๋ฅผ ํƒญํ•ด์„œ ์ฆ๊ฒจ์ฐพ๊ธฐ์— ์ถ”๊ฐ€ํ•˜์„ธ์š”")
    }
    
    var image: Image? {
        Image(systemName: "heart")
    }
}

2. ํŒ ํ‘œ์‹œ

struct ContentView: View {
    let favoriteTip = FavoriteTip()
    
    var body: some View {
        VStack {
            // ์ธ๋ผ์ธ ํŒ
            TipView(favoriteTip)
            
            Button {
                // ์•ก์…˜
            } label: {
                Image(systemName: "heart")
            }
            // ํŒ์˜ค๋ฒ„ ํŒ
            .popoverTip(favoriteTip)
        }
    }
}

3. ํŒ ๋ฌดํšจํ™”

struct FavoriteTip: Tip {
    // ...
}

// ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋Šฅ ์‚ฌ์šฉ ์‹œ ํŒ ๋‹ซ๊ธฐ
Button("์ฆ๊ฒจ์ฐพ๊ธฐ") {
    FavoriteTip().invalidate(reason: .actionPerformed)
}

// ๋ฌดํšจํ™” ์ด์œ 
// .actionPerformed: ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋Šฅ ์‚ฌ์šฉ
// .displayCountExceeded: ํ‘œ์‹œ ํšŸ์ˆ˜ ์ดˆ๊ณผ
// .tipClosed: ์‚ฌ์šฉ์ž๊ฐ€ ํŒ ๋‹ซ์Œ

์ „์ฒด ์ž‘๋™ ์˜ˆ์ œ

import SwiftUI
import TipKit

// MARK: - Tips ์ •์˜
struct SearchTip: Tip {
    var title: Text {
        Text("๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ")
    }
    
    var message: Text? {
        Text("์›ํ•˜๋Š” ํ•ญ๋ชฉ์„ ๋น ๋ฅด๊ฒŒ ์ฐพ์•„๋ณด์„ธ์š”")
    }
    
    var image: Image? {
        Image(systemName: "magnifyingglass")
    }
}

struct FilterTip: Tip {
    // ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์กฐ๊ฑด ์„ค์ •
    @Parameter
    static var hasUsedSearch: Bool = false
    
    var title: Text {
        Text("ํ•„ํ„ฐ ๊ธฐ๋Šฅ")
    }
    
    var message: Text? {
        Text("์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์–ด์š”")
    }
    
    var image: Image? {
        Image(systemName: "line.3.horizontal.decrease.circle")
    }
    
    // ํ‘œ์‹œ ์กฐ๊ฑด: ๊ฒ€์ƒ‰์„ ์‚ฌ์šฉํ•œ ํ›„์—๋งŒ
    var rules: [Rule] {
        #Rule(Self.$hasUsedSearch) { $0 == true }
    }
}

struct ShareTip: Tip {
    // ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์กฐ๊ฑด
    static let itemViewed = Event(id: "itemViewed")
    
    var title: Text {
        Text("๊ณต์œ ํ•˜๊ธฐ")
    }
    
    var message: Text? {
        Text("์นœ๊ตฌ์—๊ฒŒ ๊ณต์œ ํ•ด๋ณด์„ธ์š”")
    }
    
    var image: Image? {
        Image(systemName: "square.and.arrow.up")
    }
    
    // 3๋ฒˆ ์ด์ƒ ์•„์ดํ…œ์„ ๋ณธ ํ›„์—๋งŒ
    var rules: [Rule] {
        #Rule(Self.itemViewed) { $0.donations.count >= 3 }
    }
    
    // ํ‘œ์‹œ ์˜ต์…˜
    var options: [TipOption] {
        MaxDisplayCount(3)  // ์ตœ๋Œ€ 3๋ฒˆ๋งŒ ํ‘œ์‹œ
    }
}

struct ProTip: Tip {
    var title: Text {
        Text("Pro ๊ธฐ๋Šฅ โœจ")
    }
    
    var message: Text? {
        Text("๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด๋ณด์„ธ์š”")
    }
    
    // ์•ก์…˜ ๋ฒ„ํŠผ
    var actions: [Action] {
        Action(id: "learn-more", title: "์ž์„ธํžˆ ๋ณด๊ธฐ")
        Action(id: "dismiss", title: "๋‚˜์ค‘์—", role: .cancel)
    }
}

// MARK: - App
@main
struct TipDemoApp: App {
    var body: some Scene {
        WindowGroup {
            TipDemoView()
                .task {
                    try? Tips.configure([
                        .displayFrequency(.immediate)
                    ])
                }
        }
    }
}

// MARK: - Views
struct TipDemoView: View {
    let searchTip = SearchTip()
    let filterTip = FilterTip()
    let shareTip = ShareTip()
    let proTip = ProTip()
    
    @State private var searchText = ""
    @State private var items = ["์‚ฌ๊ณผ", "๋ฐ”๋‚˜๋‚˜", "์˜ค๋ Œ์ง€", "ํฌ๋„", "์ˆ˜๋ฐ•"]
    
    var body: some View {
        NavigationStack {
            VStack(spacing: 0) {
                // ์ธ๋ผ์ธ ํŒ (์ƒ๋‹จ)
                TipView(proTip) { action in
                    if action.id == "learn-more" {
                        // Pro ํŽ˜์ด์ง€๋กœ ์ด๋™
                    }
                }
                .tipBackground(Color.blue.opacity(0.1))
                .padding()
                
                List {
                    ForEach(filteredItems, id: \.self) { item in
                        Text(item)
                            .onTapGesture {
                                // ์•„์ดํ…œ ์กฐํšŒ ์ด๋ฒคํŠธ ๊ธฐ๋ก
                                ShareTip.itemViewed.sendDonation()
                            }
                    }
                }
            }
            .navigationTitle("TipKit ๋ฐ๋ชจ")
            .searchable(text: $searchText, prompt: "๊ฒ€์ƒ‰")
            .onChange(of: searchText) { _, newValue in
                if !newValue.isEmpty {
                    // ๊ฒ€์ƒ‰ ์‚ฌ์šฉ ๊ธฐ๋ก
                    FilterTip.hasUsedSearch = true
                    searchTip.invalidate(reason: .actionPerformed)
                }
            }
            .toolbar {
                // ๊ฒ€์ƒ‰ ๋ฒ„ํŠผ + ํŒ์˜ค๋ฒ„ ํŒ
                Button {
                    // ๊ฒ€์ƒ‰ ํฌ์ปค์Šค
                } label: {
                    Image(systemName: "magnifyingglass")
                }
                .popoverTip(searchTip)
                
                // ํ•„ํ„ฐ ๋ฒ„ํŠผ + ํŒ์˜ค๋ฒ„ ํŒ
                Button {
                    // ํ•„ํ„ฐ ์‹œํŠธ
                } label: {
                    Image(systemName: "line.3.horizontal.decrease.circle")
                }
                .popoverTip(filterTip)
                
                // ๊ณต์œ  ๋ฒ„ํŠผ + ํŒ์˜ค๋ฒ„ ํŒ
                Button {
                    shareTip.invalidate(reason: .actionPerformed)
                } label: {
                    Image(systemName: "square.and.arrow.up")
                }
                .popoverTip(shareTip)
            }
        }
    }
    
    var filteredItems: [String] {
        if searchText.isEmpty {
            return items
        }
        return items.filter { $0.contains(searchText) }
    }
}

๊ณ ๊ธ‰ ํŒจํ„ด

1. ์กฐ๊ฑด๋ถ€ ๊ทœ์น™ ์กฐํ•ฉ

struct AdvancedTip: Tip {
    @Parameter
    static var isLoggedIn: Bool = false
    
    @Parameter
    static var hasCompletedOnboarding: Bool = false
    
    static let featureUsed = Event(id: "featureUsed")
    
    var title: Text { Text("๊ณ ๊ธ‰ ๊ธฐ๋Šฅ") }
    
    var rules: [Rule] {
        // ๋กœ๊ทธ์ธ AND ์˜จ๋ณด๋”ฉ ์™„๋ฃŒ AND ๊ธฐ๋Šฅ 2๋ฒˆ ์ด์ƒ ์‚ฌ์šฉ
        #Rule(Self.$isLoggedIn) { $0 == true }
        #Rule(Self.$hasCompletedOnboarding) { $0 == true }
        #Rule(Self.featureUsed) { $0.donations.count >= 2 }
    }
}

2. ๋‚ ์งœ ๊ธฐ๋ฐ˜ ์กฐ๊ฑด

struct DailyTip: Tip {
    static let appOpened = Event(id: "appOpened")
    
    var title: Text { Text("์˜ค๋Š˜์˜ ํŒ") }
    
    var rules: [Rule] {
        // ์˜ค๋Š˜ ์•ฑ์„ ์—ด์—ˆ์„ ๋•Œ๋งŒ
        #Rule(Self.appOpened) {
            $0.donations.filter {
                Calendar.current.isDateInToday($0.date)
            }.count >= 1
        }
    }
}

3. ์ปค์Šคํ…€ ์Šคํƒ€์ผ

struct StyledTipView: View {
    let tip: some Tip
    
    var body: some View {
        TipView(tip)
            .tipBackground(
                LinearGradient(
                    colors: [.blue.opacity(0.2), .purple.opacity(0.2)],
                    startPoint: .leading,
                    endPoint: .trailing
                )
            )
            .tipImageSize(CGSize(width: 40, height: 40))
            .tipCornerRadius(16)
    }
}

4. ๋””๋ฒ„๊น… ๋ฐ ํ…Œ์ŠคํŠธ

// ๋ชจ๋“  ํŒ ๋ฆฌ์…‹ (๊ฐœ๋ฐœ์šฉ)
try? Tips.resetDatastore()

// ํŠน์ • ํŒ ํ‘œ์‹œ ๊ฐ•์ œ
Tips.showAllTipsForTesting()

// ํŒ ์ˆจ๊ธฐ๊ธฐ
Tips.hideAllTipsForTesting()

// ํŒ ์ƒํƒœ ํ™•์ธ
if myTip.shouldDisplay {
    // ํŒ์ด ํ‘œ์‹œ๋˜์–ด์•ผ ํ•จ
}

5. ํŒ ๊ทธ๋ฃน ์šฐ์„ ์ˆœ์œ„

struct HighPriorityTip: Tip {
    var title: Text { Text("์ค‘์š”ํ•œ ํŒ") }
    
    var options: [TipOption] {
        IgnoresDisplayFrequency(true)  // ๋นˆ๋„ ์ œํ•œ ๋ฌด์‹œ
    }
}

struct LowPriorityTip: Tip {
    var title: Text { Text("์ผ๋ฐ˜ ํŒ") }
    
    var options: [TipOption] {
        MaxDisplayCount(1)  // 1๋ฒˆ๋งŒ ํ‘œ์‹œ
    }
}

์ฃผ์˜์‚ฌํ•ญ

  1. Tips.configure() ํ•„์ˆ˜

    • ์•ฑ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ ํ˜ธ์ถœ
    • ๋ฏธํ˜ธ์ถœ ์‹œ ํŒ์ด ํ‘œ์‹œ๋˜์ง€ ์•Š์Œ
  2. displayFrequency ์„ค์ •

    • .immediate: ์กฐ๊ฑด ์ถฉ์กฑ ์‹œ ์ฆ‰์‹œ
    • .daily: ํ•˜๋ฃจ 1ํšŒ
    • .weekly: ์ฃผ 1ํšŒ
    • .monthly: ์›” 1ํšŒ
  3. ๋ฐ์ดํ„ฐ ์ €์žฅ ์œ„์น˜

    .datastoreLocation(.applicationDefault)  // ๊ธฐ๋ณธ
    .datastoreLocation(.groupContainer(identifier: "group.com.app"))  // App Group
  4. iOS 17+ ์ „์šฉ

    • iOS 16 ์ดํ•˜๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€
    • ์กฐ๊ฑด๋ถ€ import ๋˜๋Š” @available ์‚ฌ์šฉ