请参阅我提供的示例,我已经尽可能准确地重新创建了我的模式,同时省略了与问题无关的细节。
我的视图模型中有@Published
属性变量,这些变量在获取firebase后更新/分配。每次访问根视图或子视图时,获取逻辑都会运行(或从缓存中获取(,然后将我的值映射到视图模型中的@Published
字典。我担心的是,我的CardView
总是成功更新,而我的AlternateCardView
只在第一次加载时从字典中获得正确的值,但除非我终止应用程序,否则永远不会再更新。
我是不是错过了一个明显的最佳实践?有没有更好的方法来实现我的模式以避免这个错误?我希望每当检测到更改时,我的AlternateCardView
都会更新,并且我已经验证了我的视图模型确实在更新值——它们只是没有转化为我的视图。
请注意:我也尝试过使用自定义结构的托管集合而不是示例中提供的文字词典来实现此解决方案。尽管如此,我所描述的bug仍然存在——所以我确信这不是问题所在。我这么做是因为我认为它可以保证发射objectWillChange
,但我想知道我是否真的在SwiftUI上遇到了一个奇怪的问题
我使用的是Xcode版本13.2.1,Swift5.1,并在iOS15 iPhone 11模拟器上运行。
内容视图:
struct ContentView: View {
// ...
var body: some View {
VStack {
RootView().environmentObject(ProgressEngine())
}
}
}
根视图:
struct RootView: View {
@EnvironmentObject var userProgress: ProgressEngine
var body: some View {
VStack {
NavigationLink(destination: ChildView().environmentObject(self.userProgress)) {
CardView(progressValue: self.$userProgress.progressValues)
}
}
.onAppear {
self.userProgress.fetchAllProgress() // This is fetching data from firebase, assigns to my @Published properties
}
}
}
卡片视图:
// This view works and updates all the time, successfully - no matter how it is accessed
struct CardView: View {
@EnvironmentObject var userProgress: ProgressEngine
@Binding var progressVals: [String: CGFloat] // binding to a dict in my viewmodel
var body: some View {
VStack {
// just unwrapping for example
Text("(self.userProgress.progressValues["FirstKey"]!)")
}
}
}
子视图:
struct ChildView: View {
@EnvironmentObject var userProgress: ProgressEngine
@EnvironmentObject var anotherObject: AnotherEngine
VStack {
// I have tried this both with a ForEach and also by writing each view manually - neither works
ForEach(self.anotherObject.items.indices, id: .self) { index in
NavigationLink(destination: Text("another view").environmentObject(self.userProgress)) {
// This view only shows the expected values on first load, or if I kill and re-load the app
AlternateCardView(userWeekMap: self.$userProgress.weekMap)
}
}
}
.onAppear {
self.userProgress.fetchAllProgress()
self.userProgress.updateWeekMap()
}
AlternateCardView:
// For this example, this is basically the same as CardView,
// but shown as a unique view to replicate my situation
struct AlternateCardView: View {
@EnvironmentObject var userProgress: ProgressEngine
@Binding var weekMap: [String: [String: CGFloat]]
var body: some View {
VStack {
// just unwrapping for example
// defined it statically for the example - but dynamic in my codebase
Text("(self.userProgress.weekMap["FirstKey"]!["WeekKey1"]!)")
}
}
}
查看模型:
class ProgressEngine: ObservableObject {
// Accessing values here always works
@Published var progressValues: [String: CGFloat] = [
"FirstKey": 0,
"SecondKey": 0,
"ThirdKey": 0
]
// I am only able to read values out of this the first time view loads
// Any time my viewmodel updates this map, the changes are not reflected in my view
// I have verified that these values update in the viewmodel in time,
// To see the changes, I have to restart the app
@Published var weekMap: [String: [String: CGFloat]] = [
"FirstKey": [
"WeekKey1": 0,
"WeekKey2": 0,
"WeekKey3": 0,
.....,
.....,
],
"SecondKey": [
.....,
.....,
],
"ThirdKey": [
.....,
.....,
]
]
func fetchAllProgress(...) {
// do firebase stuff here ...
// update progressValues
}
func updateWeekMap(...) {
// Uses custom params to map data fetched from firebase to weekMap
}
}
我们不在body中初始化对象。如果模型的生存期是应用程序的,则它必须是一个singleton;如果模型的生命期应该绑定到视图,则必须是@StateObject。在您的情况下是后者,但对于这类加载器/获取器对象,我们通常不使用environmentObject,因为它们通常在深度视图结构层次结构中不共享。
请注意,ObservableObject是combine框架的一部分,因此如果您的获取不使用combine,那么您可能需要尝试使用较新的异步/等待模式,如果您将其与SwiftUI的任务修饰符配对,那么您甚至根本不需要对象!