View.environmentObject(_:) может отсутствовать как предок этого представления — но не всегда…

#ios #swift #swiftui

#iOS #swift #swiftui

Вопрос:

Я получаю эту ошибку в процессе производства и не могу найти способ ее воспроизвести.

Фатальная ошибка> Не найден ObservableObject типа PurchaseManager . View.environmentObject(_:) для PurchaseManager может отсутствовать как предок этого представления. > PurchaseManager > SwiftUI

Сбой происходит из этого представления:

 struct PaywallView: View {
    @EnvironmentObject private var purchaseManager: PurchaseManager
    var body: some View {
        // Call to purchaseManager causing the crash
    }
}
 

И это представление создается в подвидах основного представления

 @main
struct MyApp: App {
    let purchasesManager = PurchaseManager.shared
    var body: some Scene {
        WindowGroup {
            MainView()
                .environmentObject(purchasesManager)
            }
        }
    }
}
 

или, при вызове из контроллера UIKit, из этого контроллера:

 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")
    }
}
 

Я протестировал все варианты использования, которые вызывают отображение PaywallView, и у меня никогда не было сбоя.

FWIW, PurchaseManager выглядит так:

 public class PurchaseManager: ObservableObject {   
    static let shared       = PurchaseManager()
    init() {
        setupRevenueCat()
        fetchOfferings()
        refreshPurchaserInfo()
    }
}
 

Почему ObservableObject пропадает? При каких обстоятельствах?

Комментарии:

1. Может быть, из-за private . или, может быть, вы должны это сделать: .environmentObject(PurchaseManager.shared)

2. Пожалуйста, добавьте код того, как вы реализовали PurchaseManager.shared

3. @Asperi почему? что вы ищете?

4. @Asperi Готово. Я добавил реализацию.

5.возможно, некоторые функции (например, fetchOfferings ) в вашем PurchaseManager init являются асинхронными. Это может быть причиной того, что ваша проблема является прерывистой.

Ответ №1:

Причина, по которой ваша проблема является прерывистой, вероятно, заключается в том, что PurchaseManager init() может завершиться до того, как все данные будут настроены должным образом, из-за «задержек» асинхронных функций init() . Поэтому иногда данные будут доступны, когда представление этого захочет, а иногда их там не будет, и ваше приложение выйдет из строя.

Вы можете попробовать следующий подход, который включает в себя рекомендации @atultw по использованию 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"]
        }
    }
}
 

Комментарии:

1. 1 / как эти данные не установлены должным образом, поскольку для предложений есть значение по умолчанию? и 2 / почему крайний срок? Fwiw, синглтон — это временный компромисс для упрощения, поскольку приложение переписывается с UIKit на SwiftUI. Хотя мне это тоже не нравится, я, кажется, не вижу какой-либо четкой причины, почему это имеет отношение к этому сбою.

2. трудно отвечать на ваши вопросы, когда у нас нет воспроизводимого примера кода вашей проблемы, все, что я могу сделать, это строить предположения. Да, для предложений есть значение по умолчанию, но представьте, что вы выполняете цикл с использованием индексов, а затем из-за асинхронных функций индексы меняются, это может создать проблемы. Не могли бы вы показать, что вызывает сбой PaywallView , который, я думаю, может быть MainView . Лучше всего было бы показать пример кода, который показывает вашу проблему. Обратите внимание, что в моих простых тестах синглтон не был для меня серьезной проблемой.

3. это deadline всего лишь простая симуляция асинхронной функции, для завершения которой требуется немного времени, поскольку вы не показываете, что находится в fetchOfferings()

4. Спасибо @workingdod. Мне больше не с чем работать, но вы дали мне много. Я думаю, что есть проблема с тем, как fetchOfferings / refreshPurchaserInfo работают вместе. Кроме того, я заметил еще одну проблему с environmentObjects, а также это useyourloaf.com/blog/swiftui-environment-when-presenting-views .

Ответ №2:

Старайтесь не использовать здесь шаблон singleton ( .shared ), EnvironmentObject он предназначен для его замены. Вы должны создать экземпляр PurchasesManager в MyApp.

 @main
struct MyApp: App {
    @StateObject var purchasesManager = PurchaseManager() 
    var body: some Scene {
        WindowGroup {
            MainView()
                .environmentObject(purchasesManager)
            }
        }
    }
}
 

объект без состояния компилируется нормально, но необходим, если вы хотите, чтобы дочерние представления обновлялись автоматически.

Выполнение этих действий с помощью фиктивного PurchasesManager для меня работает нормально.

Комментарии:

1. Не тот ответ, который мне был нужен, но я согласен, что он должен быть одноэлементным, так что это все равно полезно и заслуживает одобрения. Спасибо