I am not sure if it is a good concept, but let's start from it here.
I have a simple View:
struct InAppPurchaseView: View {
private let viewModel = InAppPurchaseViewModel()
var body: some View {
VStack {
if !viewModel.currentProgressInfo.isEmpty {
Text(viewModel.currentProgressInfo) // here it relies on the value from viewModel and should update every time when it changes
}
}
}
}
@Observable
class InAppPurchaseViewModel {
private let transactionObserver = TransactionObserver.shared
@Binding var currentProgressInfo: String
// here is the question❓
// How to bind here property from within transactionObserver?
}
@Observable
class TransactionObserver: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
static let shared = TransactionObserver()
var currentProgressInfo = ""
// here is the code that updates currentProgressInfo depending on the needs.
}
Example of usage?
In the other view, suppose I have (totally abstract) two instances next to each other. Each has its own viewModel
struct StartView: View {
var body: some View {
VStack {
InAppPurchaseView() // here it need to be updated
InAppPurchaseView() // here it need to be updated THE SAME WAY.
}
}
}
Simply action taken in one of the above InAppPurchaseView
should impact and update another one with the same effect.
@Observable
s track changes using the getters/setters of its properties (the @Observable
macro inserts some code into the getters/setters to do this tracking). As long as a getter/setter is called during the evaluation of a view body
, SwiftUI treats that as a dependency of the view, and will update the view when that property is set again.
Your view body calls the getter of InAppPurchaseViewModel.currentProgressInfo
, but you actually want TransactionObserver.currentProgressInfo
to be a dependency of the view, so you can just have the getter of the former call the getter of the latter.
// in InAppPurchaseViewModel...
var currentProgressInfo: String {
get { transactionObserver.currentProgressInfo }
// if you also want InAppPurchaseView to be able to set the property...
// set { transactionObserver.currentProgressInfo = newValue }
}
In any case, InAppPurchaseViewModel.currentProgressInfo
should not be a stored property, because then you would end up with multiple sources of truth.
Side note: viewModel
in InAppPurchaseView
should be a @State
, and having a shared
property is not concurrency safe. I would isolate shared
to @MainActor
, and isolate the whole InAppPurchaseViewModel
class to @MainActor
.