1

I have a ContentView which uses an EnvironmentObject. After an In-App Purchase this object should be saved to the CoreData. My ContentView looks something like this:

//ContentView
import SwiftUI

struct ContentView: View {
    @EnvironmentObject var modelData: ModelData
    @Environment(\.managedObjectContext) private var viewContext
    @StateObject var storeManager: StoreManager


    var body: some View {
        Button(action: {
            purchaseAndSaveToCoreData()
        }, label: {
            Text("buy for 100")
        })
    }
    
    func purchaseAndSaveToCoreData() {
        //Open the storeManager and do the purchase
        storeManager.purchaseProduct(product: storeManager.myProducts[0])
        
        //Wait for the purchase callback and then save an object from the EnvironmentObject modelData to CoreData here <- How?
        let newItemToStore = Item(context: viewContext)
        newItemToStore.name = modelData.name
        do {
            try viewContext.save()
        } catch {
            print(error.localizedDescription)
        }

    }
}

And my StoreManager class is this:

//StoreManager
import Foundation
import StoreKit

class StoreManager: NSObject, ObservableObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
    //FETCH PRODUCTS
    //[...] Fetching Process goes here
    
    
    //HANDLE TRANSACTIONS
    @Published var transactionState: SKPaymentTransactionState?
    
    func purchaseProduct(product: SKProduct) {
        if SKPaymentQueue.canMakePayments() {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)
        } else {
            print("User can't make payment.")
        }
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchasing:
                transactionState = .purchasing
            case .purchased:
                UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
                queue.finishTransaction(transaction)
                transactionState = .purchased
                //Can I use my environmentObject modelData here?
            case .restored:
                UserDefaults.standard.setValue(true, forKey: transaction.payment.productIdentifier)
                queue.finishTransaction(transaction)
                transactionState = .restored
            case .failed, .deferred:
                print("Payment Queue Error: \(String(describing: transaction.error))")
                queue.finishTransaction(transaction)
                transactionState = .failed
            default:
                queue.finishTransaction(transaction)
            }
        }
    }
    
    func restoreProducts() {
        print("Restoring products ...")
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
}

Everything works fine, the purchase takes place. But I can't get a crucial thing to work. How can I wait inside the ContentView for a callback of the paymentQueue when it's in .purchased mode? I thought about the @Published transactionState and wanted to use it inside the purchaseAndSaveToCoreData() function like this:

//ContentView
//[...]

storeManager.purchaseProduct(product: storeManager.myProducts[0])
        
//Wait for the purchase callback and then save an object from the EnvironmentObject modelData to CoreData here <- How?
if storeManager.transactionState == .purchased {
   let newItemToStore = Item(context: viewContext)
   newItemToStore.name = modelData.name
   do {
      try viewContext.save()
   } catch {
      print(error.localizedDescription)
   }
}

But the if statement runs of course immediately after the storeManager.purchaseProduct() function and doesn't fire. I know the right timing to look for is inside the StoreManager at the case .purchased. But I can't use my EnvironmentObject there. I'm relatively new to Swift so I might be missing a key feature about Callbacks, States or triggering functions here.

J. Mann
  • 115
  • 6

1 Answers1

2

This answer pointed me in the right direction: How can I run an action when a state changes?

I changed the button in my ContentView to:

Button(action: {
   purchaseAndSaveToCoreData()
}, label: {
   Text("buy for 100")
})
.onChange(of: storeManager.transactionState) { transactionState in
   if transactionState == .purchased {
      saveVirginKeyToCoreData()
   }
}

The onChange attribute listens for the state change of transactionState.

J. Mann
  • 115
  • 6