【问题标题】:SwiftUI View does not updated when ObservedObject changedObservedObject 更改时 SwiftUI 视图未更新
【发布时间】:2020-05-02 09:10:24
【问题描述】:

我有一个内容视图,我在其中使用 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)
    }

对此有何解释?

【问题讨论】:

  • 我面临着完全相同的问题。虽然没有运气。我已经坚持了好几天了。 ViewModel 在网络调用后更新,但列表在此之后停止更新。当我在网络调用后打印对象时,它给了我预期的值,但视图没有更新。
  • @Ishmeet 如果您找到解决此问题的方法,请告诉我
  • 我还没有找到解决方案。我认为这是一个 SwiftUI 错误。
  • @Ishmeet - 你们现在找到解决方案了吗?我有同样的问题,但我使用的是列表视图。我的列表始终有 2 个单元格,并且单元格正确加载了静态信息,当我点击 Web 服务并更新模型时,视图不再重新调整。当我打印模型对象时,它们会使用最新数据进行更新。如果你们找到任何解决方案,请更新帖子
  • @Dinakar 我们确实找到了解决方案,通过重构使用正确的 State/ObservableObject/Binding 组合。我们确保我们的对象作为 State 归父级所有。但是,在向下游传递对象时,我们使用了 Binding 和 ObservedObject 属性包装器。来自 WWDC 的这段视频帮助我们实现了结果:developer.apple.com/videos/play/wwdc2019/226。如果您可以分享您的代码,我可能会提供更好的帮助。

标签: swift xcode mvvm observable swiftui


【解决方案1】:

什么是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])
}

【讨论】:

    【解决方案2】:

    分享一些可能有帮助的经验。

    当我使用 ScrollView + ForEach + fetch 时,在我通过其他方式触发视图刷新之前,什么都不会显示。当我提供固定数据而不获取时,它会正常显示。我通过为 ScrollView 指定 .infinity 宽度解决了这个问题。

    我的理论是 ForEach 会在 fetch 完成时重新渲染,但 ScrollView 不知何故不会调整其宽度以适应内容。

    你的情况类似。通过提供固定宽度的 Text,ForEach 可以在初始化时计算它的宽度,ScrollView 可以使用它来调整它的宽度。但在 fetch 的情况下,ForEach 的初始内容宽度为 0,ScrollView 也是如此。当 fetch 完成时,ScrollView 不会相应地更新其宽度。

    给 ScrollView 一些初始宽度应该可以解决您的问题。

    【讨论】:

    • 你刚刚为我节省了大量时间。我正在做一个火力基地项目,而这正是发生在我身上的事情。谢谢!
    【解决方案3】:

    对于水平 ScrollView,您需要固定高度或只使用条件 ScrollView,这将在 someModels.count &gt; 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)
    }
    

    ForEach 除了唯一 ID 外,没有其他方法可以通知更新。

    Text(someModels.count) 正在工作,因为它会在更改计数时通知更改。

    【讨论】:

      【解决方案4】:

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

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

      Next...ForEach跟踪id参数,所以你要确定结果后面有不同的_ids,否则ForEach没有标记为需要更新

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

      【讨论】:

      • ForEach 工作正常,当我在它之外添加另一个文本时,它显示了商店的数量,所以 id 不是问题,对于DispatchQueue,我认为我是主要的线程,我不需要它,添加文本后它也可以正常工作,这真的很奇怪
      猜你喜欢
      • 2020-10-22
      • 1970-01-01
      • 2020-12-21
      • 2020-07-12
      • 2021-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多