当ObservedObject改变时,SwiftUI视图没有更新

8

我有一个内容视图,在这个视图中,我使用ForEach显示了一个项目列表。

@ObservedObject var homeVM : HomeViewModel

var body: some View {
    ScrollView (.horizontal, showsIndicators: false) {
        HStack(spacing: 10) {
            Spacer().frame(width:8)
            ForEach(homeVM.favoriteStores , id: \._id){ item in
                StoreRowOneView(storeVM: item)
            }
            Spacer().frame(width:8)
        }
    }.frame(height: 100)
}

我的ObservableObject包含

@Published var favoriteStores = [StoreViewModel]()

StoreViewModel的定义如下

class StoreViewModel  : ObservableObject {
    @Published var store:ItemStore

    init(store:ItemStore) {
        self.store = store
    }

    var _id:String {
        return store._id!
    }
}

如果我直接填充数组,它可以正常工作,我可以看到商店列表;但如果我在网络调用(后台任务)之后填充数组,则视图没有得到通知。
 StoreApi().getFavedStores(userId: userId) { (response) in
            guard response != nil else {
                self.errorMsg = "Something wrong"
                self.error = true
                return
            }
            self.favoriteStores = (response)!.map(StoreViewModel.init)
            print("counnt favorite \(self.favoriteStores.count)")
        }

但是奇怪的是: - 如果我添加一个文本来显示数组项的计数,视图会得到通知,一切都正常工作

这就是我的意思:

    @ObservedObject var homeVM : HomeViewModel
    var body: some View {
        ScrollView (.horizontal, showsIndicators: false) {
            HStack(spacing: 10) {
                Spacer().frame(width:8)
                ForEach(homeVM.favoriteStores , id: \._id){ item in
                    StoreRowOneView(storeVM: item)
                }
                Text("\(self.homeVM.favoriteStores.count)").frame(width: 100, height: 100)
                Spacer().frame(width:8)
            }
        }.frame(height: 100)
    }

有解释吗?

1
我正在面对完全相同的问题。不过还没有运气。我已经被卡在这上面好几天了。ViewModel在网络调用后更新了,但列表在此之后停止更新。当我在网络调用后打印对象时,它给我期望的值,但视图并没有更新。 - Ishmeet
@Ishmeet 如果您找到了解决此问题的方法,请更新我。 - Ouail Bellal
@Ishmeet - 你们现在找到解决方案了吗?我也遇到了同样的问题,但我正在使用列表视图。我的列表始终有2个单元格,并且这些单元格正确加载了静态信息,当我调用Web服务并更新模型时,视图不再重新调整。当我打印模型对象时,它们已经更新为最新数据。如果你们找到了任何解决方案,请更新帖子。 - Dinakar
2
@Dinakar 我们通过重构使用正确的 State/ObservableObject/Binding 组合找到了解决方案。我们确保我们的对象由父级作为 State 拥有。然而,在向下传递对象时,我们使用了 Binding 和 ObservedObject 属性包装器。这个来自 WWDC 的视频帮助我们实现了结果:https://developer.apple.com/videos/play/wwdc2019/226/ 。如果你能分享你的代码,我可能能够更好地帮助你。 - Ishmeet
@OuailBellal,你能解决你的问题吗? - Ishmeet
显示剩余5条评论
4个回答

3

对于水平滚动视图,您需要固定高度或者只使用条件ScrollView。当 someModels.count > 0 时才会显示。

var body: some View {
   ScrollView(.horizontal) {
      HStack() {
         ForEach(someModels, id: \.someParameter1) { someModel in
            someView(someModel)
         }
      }.frame(height: someFixedHeight)
   }
}
// or
var body: some View {
   VStack {
      if someModels.count > 0 {
         ScrollView(.horizontal) {
            HStack() {
               ForEach(someModels, id: \.someParameter1) { someModel in
                  someView(someModel)
               }
            }
         }
      } else {
        EmptyView()
        // or other view
      }
   }
}

通过实现正确的哈希协议,使其符合Hashable协议要求

func hash(into hasher: inout Hasher) { 
   hasher.combine(someParameter1)
   hasher.combine(someParameter2)
}

并且。
static func == (lhs: SomeModel, rhs: SomeModel) -> Bool {
   lhs.someParameter1 == rls.someParameter1 && lhs.someParameter2 == rls.someParameter2

}

同时还要遵循@Asperi的建议,使用模型中唯一的正确ID来标识每个元素。
ForEach(someModels, id: \.someParameter1) { someModel in
    someView(someModel)
}
< p > 除了唯一的ID,ForEach没有其他通知更新的方式。 < /p> < p > Text(someModels.count) 能够工作是因为它在计数改变时通知更改。 < /p>

2
分享一些可能有用的经验。
当我使用ScrollView + ForEach + fetch时,除非我通过其他手段触发视图刷新,否则什么都不会显示。如果提供固定数据而不是获取数据,则正常显示。我通过为ScrollView指定.infinity宽度来解决了这个问题。
我的理论是,ForEach在获取完成后重新渲染,但某种方式下ScrollView不会调整其宽度以适应内容。
在您的情况下也是类似的。通过提供固定宽度文本,ForEach可以在初始化期间计算它的宽度,ScrollView可以使用它来调整它的宽度。但是,在获取时,ForEach具有初始0内容宽度,因此ScrollView也是如此。当获取完成时,ScrollView不会相应地更新其宽度。
给ScrollView一些初始宽度应该可以解决您的问题。

你刚刚为我节省了大量时间。我正在开发一个 Firebase 项目,而我也遇到了完全相同的问题。谢谢你! - テッド

0

什么是ItemStore?更重要的是什么?

ForEach(homeVM.favoriteStores , id: \._id){ item in
    StoreRowOneView(storeVM: item)
}

_id是什么?我相信你的_id每次都是相同的,所以它不会更新列表。你必须放置id:一些变量,当它改变时,你想重新渲染列表。

更新:如果你没有其他变量可以作为参考值,那么你可以使用

ForEach((0..<homeVM.favoriteStores.count)) { index in
    StoreRowOneView(storeVM:: homeVM.favoriteStores[index])
}

0

我会从将结果转发到主队列开始,如下所示

DispatchQueue.main.async {
    self.favoriteStores = (response)!.map(StoreViewModel.init)
}

下一步... ForEach 跟踪 id 参数,因此您应该确保结果在以下具有不同的 _id,否则 ForEach 将不被标记为需要更新。
ForEach(homeVM.favoriteStores , id: \._id){ item in
    StoreRowOneView(storeVM: item)
}

1
当我在它外部添加另一个文本来显示商店数量时,ForEach 就能正常工作,所以 ID 不是问题。至于 DispatchQueue ,我认为我已经在主线程上了,不需要它,而且在加入 Text 后它也能正常工作,这真的很奇怪。 - Ouail Bellal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接