A modern, SwiftUI-friendly StoreKit wrapper for iOS in-app purchases. SwiftStore simplifies the integration of subscriptions, lifetime purchases, and consumable products in your iOS applications.
- π Easy Setup: Simple configuration with product IDs and URLs
- π± SwiftUI Integration: Built-in
@SwiftStoreStateproperty wrapper - π Automatic Transaction Handling: Handles unfinished and current entitlements
- π° Multiple Product Types: Support for subscriptions, lifetime purchases, and consumables
- π‘οΈ Transaction Verification: Built-in StoreKit transaction verification
- β‘ Observable: Uses Swift's Observation framework for reactive updates
- iOS 17.0+
- Swift 6.2+
- Xcode 15.0+
Add SwiftStore to your project using Swift Package Manager:
- In Xcode, go to File β Add Package Dependencies
- Enter the repository URL:
https://github.com/your-username/swift-store - Select the version and add to your target
First, create a configuration with your App Store Connect product IDs:
import SwiftStore
let configuration = SSConfiguration()
configuration.subscriptionIDs = ["monthly_premium", "yearly_premium"]
configuration.lifetimeIDs = ["lifetime_premium"]
configuration.termsURL = "https://yourapp.com/terms"
configuration.privacyURL = "https://yourapp.com/privacy"Initialize SwiftStore in your app's entry point:
import SwiftUI
import SwiftStore
@main
struct MyApp: App {
init() {
let configuration = SSConfiguration()
configuration.subscriptionIDs = ["monthly_premium", "yearly_premium"]
configuration.lifetimeIDs = ["lifetime_premium"]
configuration.termsURL = "https://yourapp.com/terms"
configuration.privacyURL = "https://yourapp.com/privacy"
SwiftStore.shared.initialize(configuration: configuration)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}Use the @SwiftStoreState property wrapper to access store state:
import SwiftUI
import SwiftStore
struct ContentView: View {
@SwiftStoreState private var store
var body: some View {
VStack(spacing: 20) {
// Check premium status
if store.isPremium {
Text("π Premium Active!")
.font(.title)
.foregroundColor(.green)
} else {
Text("Upgrade to Premium")
.font(.title)
.foregroundColor(.blue)
}
// Show active subscription
if let subscription = store.activeSubscription {
Text("Active Subscription: \(subscription)")
}
// Show lifetime status
if store.activeLifeTime {
Text("Lifetime Premium Active")
}
// Purchase buttons
Button("Purchase Lifetime") {
// Handle lifetime purchase
}
.disabled(store.activeLifeTime)
Button("Subscribe Monthly") {
// Handle subscription purchase
}
.disabled(store.activeSubscription != nil)
}
.padding()
}
}Configuration class for setting up your in-app purchase products.
public class SSConfiguration {
/// Array of subscription product IDs from App Store Connect
public var subscriptionIDs: [String] = []
/// Array of lifetime product IDs from App Store Connect
public var lifetimeIDs: [String] = []
/// URL for Terms of Service page
public var termsURL: String?
/// URL for Privacy Policy page
public var privacyURL: String?
}Main store class that handles all in-app purchase operations.
public final class SwiftStore {
/// Shared singleton instance
static let shared = SwiftStore()
/// Whether lifetime purchase is active
public var activeLifeTime: Bool
/// Currently active subscription product ID
public var activeSubscription: String?
/// Whether user has premium access (lifetime or subscription)
public var isPremium: Bool
/// Terms of Service URL
public var termsURL: String?
/// Privacy Policy URL
public var privacyURL: String?
/// Initialize the store with configuration
public func initialize(configuration: SSConfiguration) -> SwiftStore
}Property wrapper for SwiftUI integration.
@propertyWrapper
struct SwiftStoreState: DynamicProperty {
/// Access to the SwiftStore instance
var wrappedValue: SwiftStore
/// Binding for two-way data binding
var projectedValue: Binding<SwiftStore>
}Enum representing different product types.
public enum ProductType {
case lifetime // One-time purchase
case subscription // Recurring subscription
case none // Product not found
}struct PremiumView: View {
@SwiftStoreState private var store
var body: some View {
if store.isPremium {
PremiumContentView()
} else {
UpgradePromptView()
}
}
}struct SubscriptionView: View {
@SwiftStoreState private var store
var body: some View {
VStack {
if let activeSubscription = store.activeSubscription {
Text("Active: \(activeSubscription)")
Button("Manage Subscription") {
// Open subscription management
}
} else {
Button("Subscribe Now") {
// Start subscription flow
}
}
}
}
}struct SettingsView: View {
@SwiftStoreState private var store
var body: some View {
List {
if let termsURL = store.termsURL {
Link("Terms of Service", destination: URL(string: termsURL)!)
}
if let privacyURL = store.privacyURL {
Link("Privacy Policy", destination: URL(string: privacyURL)!)
}
}
}
}The @SwiftStoreState property wrapper supports dynamic member lookup, allowing you to access properties directly:
struct MyView: View {
@SwiftStoreState private var store
var body: some View {
VStack {
// Direct property access
Text("Premium: \(isPremium)")
Text("Lifetime: \(activeLifeTime)")
if let subscription = activeSubscription {
Text("Subscription: \(subscription)")
}
}
}
}While SwiftStore handles transaction verification automatically, you can extend it for custom purchase flows:
extension SwiftStore {
func purchaseProduct(_ productID: String) async throws {
// Your custom purchase logic here
// SwiftStore will automatically handle the transaction verification
}
}SwiftStore automatically monitors transaction updates and updates the state accordingly. The store will:
- Handle unfinished transactions on app launch
- Process current entitlements
- Monitor for new transaction updates
- Update premium status automatically
- Initialize Early: Initialize SwiftStore in your app's entry point before any views are created
- Use Property Wrapper: Always use
@SwiftStoreStatein SwiftUI views for reactive updates - Handle Loading States: Consider showing loading states while transactions are being processed
- Test Thoroughly: Test with StoreKit's sandbox environment before releasing
- Handle Edge Cases: Consider what happens when network is unavailable or transactions fail
- Products Not Loading: Ensure your product IDs match exactly with App Store Connect
- Transactions Not Completing: Check that you're testing with sandbox accounts
- State Not Updating: Make sure you're using
@SwiftStoreStatein SwiftUI views
- Enable StoreKit testing in Xcode's scheme settings
- Use sandbox test accounts for testing
- Check the console for transaction verification logs
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
If you encounter any issues or have questions, please open an issue on GitHub.