๐ณ ๊ตฌ๋ ํ ์ฑ ๋ง๋ค๊ธฐ
StoreKit 2์ ํ๋์ ์ธ async/await API๋ก ๊ตฌ๋ ์์คํ ์ ๊ตฌํํฉ๋๋ค.
๐ฆ Product ๋ก๋ฉ
StoreManager.swift
func loadProducts() async { products = try await Product.products(for: productIDs) }
๐ฐ ๊ตฌ๋งค ์ฒ๋ฆฌ
Purchase.swift
let result = try await product.purchase() switch result { case .success(let verification): guard case .verified(let transaction) = verification else { return } await transaction.finish() case .userCancelled, .pending: break }
๐ ๊ตฌ๋ ์ํ ํ์ธ
SubscriptionStatus.swift
func checkSubscriptionStatus() async { for await result in Transaction.currentEntitlements { guard case .verified(let transaction) = result else { continue } if transaction.productType == .autoRenewable { // ๊ตฌ๋ ํ์ฑํ isPremium = true } } }
โป๏ธ ๊ตฌ๋งค ๋ณต์
Restore.swift
func restorePurchases() async { try? await AppStore.sync() await checkSubscriptionStatus() }
๐จ ํ์ด์ UI (HIG)
Apple HIG ํ์ด์ ๊ฐ์ด๋๋ผ์ธ: ๊ธฐ๋ฅ ๊ฐ์น ๋จผ์ ๋ณด์ฌ์ฃผ๊ธฐ, ๋ช ํํ ๊ฐ๊ฒฉ, ์ฌ์ด ๋ณต์ ๊ฒฝ๋ก
PaywallView.swift
struct PaywallView: View { @State private var products: [Product] = [] @State private var isPurchasing = false var body: some View { VStack(spacing: 20) { // ๊ธฐ๋ฅ ๊ฐ์น ๋จผ์ ๋ณด์ฌ์ฃผ๊ธฐ Text("ํ๋ฆฌ๋ฏธ์ ๊ธฐ๋ฅ") .font(.largeTitle.bold()) FeatureRow(icon: "star", text: "๋ฌด์ ํ ์ฌ์ฉ") FeatureRow(icon: "bolt", text: "๊ด๊ณ ์ ๊ฑฐ") FeatureRow(icon: "heart", text: "๋ ์ ์ฝํ ์ธ ") Spacer() // ๊ตฌ๋ ํ๋ ForEach(products, id: \.id) { product in Button { purchase(product) } label: { VStack { Text(product.displayName) Text(product.displayPrice) .font(.headline) } .frame(maxWidth: .infinity) .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(12) } } // ๋ณต์ ๋ฒํผ (HIG ํ์) Button("๊ตฌ๋งค ๋ณต์") { Task { await restorePurchases() } } .font(.caption) } .padding() .task { products = try? await Product.products(for: ["premium_monthly"]) } } func purchase(_ product: Product) { isPurchasing = true Task { let result = try await product.purchase() // ... ๊ตฌ๋งค ์ฒ๋ฆฌ isPurchasing = false } } }
๐ฏ Transaction ๋ฆฌ์ค๋
TransactionObserver.swift
func observeTransactions() async { for await result in Transaction.updates { guard case .verified(let transaction) = result else { continue } // ๊ตฌ๋งค ์๋ฃ ์ฒ๋ฆฌ if transaction.revocationDate == nil { // ํ์ฑ ๊ตฌ๋ unlockPremiumFeatures() } else { // ํ๋ถ๋จ lockPremiumFeatures() } await transaction.finish() } }
๐ก HIG ์ฒดํฌ๋ฆฌ์คํธ
โ
๊ฐ์น ๋จผ์ , ๊ฐ๊ฒฉ์ ๋์ค์
โ
๋ช
ํํ ๊ตฌ๋
๊ธฐ๊ฐ๊ณผ ๊ฐฑ์ ์ฃผ๊ธฐ
โ
๋ณต์ ๋ฒํผ ํ์
โ
"๊ตฌ๋
๊ด๋ฆฌ" ๋งํฌ ์ ๊ณต
โ
๋ฌด๋ฃ ์ฒดํ ๊ธฐ๊ฐ ๋ช
์