I'm getting this error in production and can't find a way to reproduce it.
Fatal error > No ObservableObject of type PurchaseManager found. A View.environmentObject(_:) for PurchaseManager may be missing as an ancestor of this view. > PurchaseManager > SwiftUI
The crash comes from this view:
struct PaywallView: View {
@EnvironmentObject private var purchaseManager: PurchaseManager
var body: some View {
// Call to purchaseManager causing the crash
}
}
And this view is instantiated in subviews of the MainView
@main
struct MyApp: App {
let purchasesManager = PurchaseManager.shared
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(purchasesManager)
}
}
}
}
or, when called from a UIKit controller, from this controler:
final class PaywallHostingController: UIHostingController<AnyView> {
init() {
super.init(rootView:
AnyView(
PaywallView()
.environmentObject(PurchaseManager.shared)
)
)
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I tested all the use cases that trigger the PaywallView to show up, and I never got a crash.
FWIW, the PurchaseManager looks like this:
public class PurchaseManager: ObservableObject {
static let shared = PurchaseManager()
init() {
setupRevenueCat()
fetchOfferings()
refreshPurchaserInfo()
}
}
Why would the ObservableObject go missing? In which circumstances?
CodePudding user response:
Try not to use the singleton pattern here (.shared
), EnvironmentObject
is meant to be a replacement for it. You should instantiate PurchasesManager in MyApp.
@main
struct MyApp: App {
@StateObject var purchasesManager = PurchaseManager()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(purchasesManager)
}
}
}
}
without state object compiles fine but needed if you want child views to update automatically.
Doing those things with a dummy PurchasesManager runs fine for me.
CodePudding user response:
The reason your problem is intermittent, is probably because the PurchaseManager
init()
could finish
before all the data is setup properly, due to the "delays" of the
async functions in init()
. So sometimes the data will be available
when the View wants it, and sometimes it will not be there and crash your app.
You could try the following approach that includes @atultw advice of using StateObject.
import SwiftUI
@main
struct TestApp: App {
@StateObject var purchaseManager = PurchaseManager() // <-- here
var body: some Scene {
WindowGroup {
MainView()
.onAppear {
purchaseManager.startMeUp() // <-- here
}
.environmentObject(purchaseManager)
}
}
}
struct MainView: View {
@EnvironmentObject var purchaseManager: PurchaseManager
var body: some View {
Text("testing")
List {
ForEach(purchaseManager.offerings, id: \.self) { offer in
Text(offer)
}
}
}
}
public class PurchaseManager: ObservableObject {
@Published var offerings: [String] = []
// -- here --
func startMeUp() {
// setupRevenueCat()
fetchOfferings()
// refreshPurchaserInfo()
}
func fetchOfferings() {
DispatchQueue.main.asyncAfter(deadline: .now() 2) {
self.offerings = ["offer 1","offer 2","offer 3","offer 4"]
}
}
}