Published computed properties in SwiftUI model objects

前端 未结 3 1580
南笙
南笙 2021-01-07 03:38

Suppose I have a data model in my SwiftUI app that looks like the following:

class Tallies: Identifiable, ObservableObject {
  let id = UUID()
  @Published va         


        
3条回答
  •  野趣味
    野趣味 (楼主)
    2021-01-07 03:56

    There are multiple issues here to address.

    First, it's important to understand that SwiftUI updates the view's body when it detects a change, either in a @State property, or from an ObservableObject (via @ObservedObject and @EnvironmentObject property wrappers).

    In the latter case, this is done either via a @Published property, or manually with objectWillChange.send(). objectWillChange is an ObservableObjectPublisher publisher available on any ObservableObject.

    This is a long way of saying that IF the change in a computed property is caused together with a change of any @Published property - for example, when another element is added from somewhere:

    elements.append(Talies())
    

    then there's no need to do anything else - SwiftUI will recompute the view that observes it, and will read the new value of the computed property cumulativeCount.


    Of course, if the .count property of one of the Tallies objects changes, this would NOT cause a change in elements, because Tallies is a reference-type.

    The best approach given your simplified example is actually to make it a value-type - a struct:

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

    Now, a change in any of the Tallies objects would cause a change in elements, which will cause the view that "observes" it to get the now-new value of the computed property. Again, no extra work needed.


    If you insist, however, that Tallies cannot be a value-type for whatever reason, then you'd need to listen to any changes in Tallies by subscribing to their .objectWillChange publishers:

    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
    
       var cumulativeCount: Int {
         return elements.reduce(0) { $0 + $1.count } // no changes here
       }
    } 
    

    The above will subscribe a change in the elements array (to account for additions and removals) by:

    • converting the array into a Sequence publisher of each array element
    • then flatMap again each array element, which is a Tallies object, into its objectWillChange publisher
    • then for any output, call objectWillChange.send(), to notify of the view that observes it of its own changes.

提交回复
热议问题