【问题标题】:Publishing changes to a collection of observable objects将更改发布到可观察对象的集合
【发布时间】:2021-06-23 15:41:56
【问题描述】:

我在传播视图模型中保存在数组中的对象上发生的更改时遇到问题。

我知道如果集合本身发生变化,集合的@Published 将起作用(例如,如果元素是struct 而不是class)。假设我需要将类保留为类。有没有办法将事件传播到视图,以便它知道应该刷新它。

我一直在尝试所有令人讨厌的方法,例如实现 ObservableCollectionObservableArray,但似乎没有任何效果。

下面是我正在努力解决的一个例子。 Toggle 正在更改具有所有 ObservableObject 一致性和 @Published 注释但仍没有刷新 Text 的数组的内部元素。

import SwiftUI
import Combine

struct ContentView: View {
    @StateObject var vm = ViewModel()
    
    var body: some View {
        Text(vm.texts.first!.text)
            .padding()
        
        Button("Toggle") {
            vm.texts.first?.toggle()
        }
    }
}

class ViewModel: ObservableObject {
    @Published var texts: [TextHolder] = [.init(), .init()]
}

class TextHolder: ObservableObject {
    @Published var text: String = ""
    
    func toggle() {
        text = UUID().uuidString
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

【问题讨论】:

  • 三个答案中的任何一个是否回答了您的问题或提供了任何帮助?我注意到他们都没有任何赞成票或 cmets。

标签: swift swiftui combine


【解决方案1】:

您的方法的问题是 TextHolder 是一个类,它是引用类型,如果您更改其中的任何值,更改将不会反映到数组,这就是 SwiftUI 视图未更新的原因。

方法一:

您可以将 TextHolder 从 class 更改为 struct,如果您更改 struct 中的任何值,则会创建一个新副本,您的数组将获得更改为您的 SwiftUI 视图。

请尝试以下代码

struct ContentView: View {
    @StateObject var vm = ViewModel()
    
    var body: some View {
        Text(vm.texts.first!.text)
            .padding()
        
        Button("Toggle") {
            vm.texts[0].toggle()
        }
    }
}

class ViewModel: ObservableObject {
    @Published var texts: [TextHolder] = [.init(), .init()]
}

struct TextHolder {
    var text: String = ""
    
    mutating func toggle() {
        text = UUID().uuidString
    }
}

方法二:

更改值后,您必须手动告诉您的 viewModel 某些内容已更改,请刷新。

Button("Toggle") {
     vm.texts[0].toggle()
     vm.objectWillChange.send()
}

希望能帮助你理解。

【讨论】:

    【解决方案2】:

    注意:这是基于您列出的“假设我需要将类保留为类”的要求——否则,将您的模型设为结构会免费为您提供所有这些行为。

    您可以在ObservableObject 上手动拨打objectWillChange.send()。例如:

    Button("Toggle") {
        vm.texts.first?.toggle()
        vm.objectWillChange.send()
    }
    

    主要缺点包括必须在每个突变位点添加代码来调用它,并且实际上要记住这样做。您可以做一些事情来划分代码,就像将toggle 移动到父对象并将索引传递给它——然后,您可以将所有objectWillChange 调用保留在父对象中。此外,您可以尝试使用 KVO 来观察子对象的属性,并在看到其中一个对象发生变化时调用 objectWillChange

    【讨论】:

      【解决方案3】:

      如果您无法将类转换为结构,您可以采取的一种方法是订阅数组中项目的所有 objectWillChange 发布者,并在其中一个对象时为主模型发出一个改变:

      @Published var texts: [TextHolder] = [.init(), .init()] {
          didSet {
              updateTextsSubscriptions()
          }
      }
      
      private var textsSubscriptions = [AnyCancellable]()
      
      private func updateTextsSubscriptions() {
          textsSubscriptions = texts.map {
              $0.objectWillChange.sink(receiveValue: {
                  self.objectWillChange.send()
              })
          }
      }
      

      您还需要在初始化程序中调用 updateTextsSubscriptions,以确保 texts 数组的任何初始值都受到监控:

      init() {
          updateTextsSubscriptions()
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多