A lightweight StoreKit2 wrapper designed specifically for SwiftUI, making in-app purchases implementation simpler and more intuitive.
Please refer to the detailed StoreKitHelper documentation in DevTutor, which includes multiple quick start examples, custom payment interface examples, and API references, providing comprehensive examples and guidance.
- 🚀 SwiftUI Native: Designed specifically for SwiftUI with
@ObservableObjectand@EnvironmentObjectsupport - 💡 Simple API: Clean and intuitive interface for managing in-app purchases
- 🔄 Automatic Updates: Real-time transaction monitoring and status updates
- ✅ Type Safe: Protocol-based product definitions with compile-time safety
- 🧪 Testable: Fully testable architecture with comprehensive test case coverage ExampleTests.swift/StoreKitHelperTests.swift
Create and inject a StoreContext instance at your SwiftUI app's entry point, which is responsible for loading the product list and tracking purchase status.
import StoreKitHelper
enum AppProduct: String, InAppProduct {
case lifetime = "focuscursor.lifetime"
case monthly = "focuscursor.monthly"
var id: String { rawValue }
}
@main struct DevTutorApp: App {
@StateObject var store = StoreContext(products: AppProduct.allCases)
var body: some Scene {
WindowGroup {
ContentView().environmentObject(store)
}
}
}You can use the hasNotPurchased or hasPurchased properties in StoreContext to check if the user has made a purchase, then dynamically display different interface content. For example:
@EnvironmentObject var store: StoreContext
var body: some View {
if store.hasNotPurchased == true {
// 🧾 User hasn't purchased - show limited content or purchase prompt
} else {
// ✅ User has purchased - show full functionality
}
if store.hasPurchased == true {
// ✅ User has purchased - show full functionality
} else {
// 🧾 User hasn't purchased - show limited content or purchase prompt
}
}Use StoreKitHelperView to directly display in-app purchase popup views and configure various parameters through a chainable API.
struct PurchaseContent: View {
@EnvironmentObject var store: StoreContext
var body: some View {
let locale: Locale = Locale(identifier: Locale.preferredLanguages.first ?? "en")
StoreKitHelperView()
.environment(\.locale, .init(identifier: locale.identifier))
.environment(\.pricingContent, { AnyView(PricingContent()) })
.environment(\.popupDismissHandle, {
// Triggered when the popup is dismissed
// (e.g., user clicks the close button)
store.isShowingPurchasePopup = false
})
.environment(\.termsOfServiceHandle, {
// Action triggered when the [Terms of Service] button is clicked
})
.environment(\.privacyPolicyHandle, {
// Action triggered when the [Privacy Policy] button is clicked
})
.frame(maxWidth: 300)
.frame(minWidth: 260)
}
}Click to open the paid product list interface.
struct ContentView: View {
@EnvironmentObject var store: StoreContext
var body: some View {
if store.hasNotPurchased == true, store.isLoading == false {
PurchasePopupButton()
.sheet(isPresented: $store.isShowingPurchasePopup) {
PurchaseContent()
}
}
}
}Similar to StoreKitHelperView, but for selecting purchase items to make payments.
struct PurchaseContent: View {
@EnvironmentObject var store: StoreContext
var body: some View {
let locale: Locale = Locale(identifier: Locale.preferredLanguages.first ?? "en")
StoreKitHelperSelectionView()
.environment(\.locale, .init(identifier: locale.identifier))
.environment(\.pricingContent, { AnyView(PricingContent()) })
.environment(\.popupDismissHandle, {
// Triggered when the popup is dismissed
// (e.g., user clicks the close button)
store.isShowingPurchasePopup = false
})
.environment(\.termsOfServiceHandle, {
// Action triggered when the [Terms of Service] button is clicked
})
.environment(\.privacyPolicyHandle, {
// Action triggered when the [Privacy Policy] button is clicked
})
.frame(maxWidth: 300)
.frame(minWidth: 260)
}
}protocol InAppProduct: CaseIterable {
var id: String { get }
}products: [Product]- Available products from the App StorepurchasedProductIDs: Set<String>- Set of purchased product identifiershasNotPurchased: Bool- Whether the user hasn't purchased any productshasPurchased: Bool- Whether the user has purchased any productsisLoading: Bool- Whether products are currently loadingerrorMessage: String?- Current error message, if any
purchase(_ product: Product)- Purchase a specific productrestorePurchases()- Restore previous purchasesisPurchased(_ productID: ProductID) -> Bool- Check if a product is purchased by IDisPurchased(_ product: InAppProduct) -> Bool- Check if a product is purchasedproduct(for productID: ProductID) -> Product?- Get product by IDproduct(for product: InAppProduct) -> Product?- Get product by InAppProduct
Licensed under the MIT License.
