首页 > 解决方案 > 在 SwiftUI 模型对象中发布的计算属性

问题描述

假设我的 SwiftUI 应用程序中有一个数据模型,如下所示:

class Tallies: Identifiable, ObservableObject {
  let id = UUID()
  @Published var count = 0
}

class GroupOfTallies: Identifiable, ObservableObject {
  let id = UUID()
  @Published var elements: [Tallies] = []
}

我想添加一个计算属性GroupOfTallies,类似于以下内容:

// Returns the sum of counts of all elements in the group
var cumulativeCount: Int {
  return elements.reduce(0) { $0 + $1.count }
}

但是,我希望 SwiftUI 在cumulativeCount更改时更新视图。这会在elements更改(数组获得或丢失元素)或count任何包含Tallies对象的字段更改时发生。

我已经研究过将其表示为AnyPublisher,但我认为我对 Combine 的掌握不够好,无法使其正常工作。这个答案中提到了这一点,但是从中创建的 AnyPublisher 是基于已发布的Double而不是已发布的Array。如果我尝试使用相同的方法而不进行修改,cumulativeCount则仅在元素数组更改时更新,而不是在count元素之一的属性更改时更新。

标签: swiftswiftuicombine

解决方案


这里有多个问题需要解决。

首先,重要的是要了解 SwiftUI 在检测到更改时会更新视图的主体,无论是在@State属性中还是来自ObservableObject(通过@ObservedObject@EnvironmentObject属性包装器)。

在后一种情况下,这可以通过@Published属性或手动使用objectWillChange.send(). objectWillChangeObservableObjectPublisher任何ObservableObject.

这是一个很长的说法,如果计算属性的更改是与任何属性的更改一起@Published引起的- 例如,当从某个地方添加另一个元素时:

elements.append(Talies())

那么就不需要做任何其他事情了——SwiftUI 将重新计算观察它的视图,并读取计算属性的新值cumulativeCount


当然,如果.count其中一个对象的属性Tallies发生变化,这不会导致 的变化elements,因为Tallies它是一个引用类型。

给出您的简化示例的最佳方法实际上是使其成为值类型 - a struct

struct Tallies: Identifiable {
  let id = UUID()
  var count = 0
}

现在,任何Tallies对象的更改都会导致 的更改elements,这将导致“观察”它的视图获取计算属性的新值。同样,不需要额外的工作。


但是,如果您坚持认为,无论Tallies出于何种原因,它都不能成为值类型,那么您需要Tallies通过订阅其.objectWillChange发布者来监听任何更改:

class GroupOfTallies: Identifiable, ObservableObject {
   let id = UUID()
   @Published var elements: [Tallies] = [] {
      didSet {
         cancellables = [] // cancel the previous subscription
         elements.publisher
            .flatMap { $0.objectWillChange }
            .sink(receiveValue: self.objectWillChange.send) 
            .store(in: &cancellables)
      }
   }

   private var cancellables = Set<AnyCancellable>

   var cumulativeCount: Int {
     return elements.reduce(0) { $0 + $1.count } // no changes here
   }
} 

以上将通过以下方式订阅elements数组中的更改(以考虑添加和删除):

  • 将数组转换Sequence为每个数组元素的发布者
  • 然后 flatMap 再次将每个数组元素(这是一个Tallies对象)放入其objectWillChange发布者
  • 然后对于任何输出,调用objectWillChange.send(),通知观察它自己的变化的视图。

推荐阅读