#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. Не тот ответ, который мне был нужен, но я согласен, что он должен быть одноэлементным, так что это все равно полезно и заслуживает одобрения. Спасибо