更改ObservedObject时,SwiftUI View不会更新

时间:2020-01-15 15:05:07

标签: swift xcode mvvm observable swiftui

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

对此有何解释?

4 个答案:

答案 0 :(得分:2)

对于水平ScrollView,您需要固定其高度,或者只使用有条件的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
      }
   }
}

通过实现

正确符合哈希协议
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,该ID必须对每个元素都是唯一的。

ForEach(someModels, id: \.someParameter1) { someModel in
    someView(someModel)
}

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

Text(someModels.count)之所以有效,是因为它在更改计数时通知更改。

答案 1 :(得分:2)

分享一些可能有用的经验。

当我使用ScrollView + ForEach + fetch时,直到我通过其他方式触发视图刷新之前,什么都没有显示。当我提供固定数据而不进行提取时,它会正常显示。我通过为ScrollView指定.infinity宽度来解决它。

我的理论是,ForEach在获取完成时会重新渲染,但是ScrollView某种程度上不会调整其宽度以适合内容。

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

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

答案 2 :(得分:0)

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

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

下一步... ForEach跟踪id参数,因此您应确保以下结果具有不同的_id,否则ForEach未标记为需要更新

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

答案 3 :(得分: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])
}