将更改发布到可观察对象的集合

Posted

技术标签:

【中文标题】将更改发布到可观察对象的集合【英文标题】: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。 【参考方案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()

【讨论】:

以上是关于将更改发布到可观察对象的集合的主要内容,如果未能解决你的问题,请参考以下文章

将图像从隔离存储加载到可观察集合不起作用

DataTable 到可观察的集合

为啥我的 ListView 数据绑定到可观察集合不能正确显示

WPF将单个文本框绑定到集合对象或数组中的元素

在 Xamarin c# 中滚动到可观察集合中的选定项目

从 txt 文件中读取数据并将其添加到 Observable 集合中