Why an ObservedObject array is not updated in my SwiftUI application?

前端 未结 4 1471
被撕碎了的回忆
被撕碎了的回忆 2020-11-30 00:53

I\'m playing with SwitUI, trying to understand how ObservableObject works. I have an array of Person objects. When I add a new Person into the array, it is reloaded in my Vi

4条回答
  •  北荒
    北荒 (楼主)
    2020-11-30 01:32

    ObservableArray is very useful, thank you! Here's a more generalised version that supports all Collections, which is handy when you need to react to CoreData values indirected through a to-many relationship (which are modelled as Sets).

    import Combine
    import SwiftUI
    
    private class ObservedObjectCollectionBox: ObservableObject where Element: ObservableObject {
        private var subscription: AnyCancellable?
        
        init(_ wrappedValue: AnyCollection) {
            self.reset(wrappedValue)
        }
        
        func reset(_ newValue: AnyCollection) {
            self.subscription = Publishers.MergeMany(newValue.map{ $0.objectWillChange })
                .eraseToAnyPublisher()
                .sink { _ in
                    self.objectWillChange.send()
                }
        }
    }
    
    @propertyWrapper
    public struct ObservedObjectCollection: DynamicProperty where Element: ObservableObject {
        public var wrappedValue: AnyCollection {
            didSet {
                if isKnownUniquelyReferenced(&observed) {
                    self.observed.reset(wrappedValue)
                } else {
                    self.observed = ObservedObjectCollectionBox(wrappedValue)
                }
            }
        }
        
        @ObservedObject private var observed: ObservedObjectCollectionBox
    
        public init(wrappedValue: AnyCollection) {
            self.wrappedValue = wrappedValue
            self.observed = ObservedObjectCollectionBox(wrappedValue)
        }
        
        public init(wrappedValue: AnyCollection?) {
            self.init(wrappedValue: wrappedValue ?? AnyCollection([]))
        }
        
        public init(wrappedValue: C) where C.Element == Element {
            self.init(wrappedValue: AnyCollection(wrappedValue))
        }
        
        public init(wrappedValue: C?) where C.Element == Element {
            if let wrappedValue = wrappedValue {
                self.init(wrappedValue: wrappedValue)
            } else {
                self.init(wrappedValue: AnyCollection([]))
            }
        }
    }
    

    It can be used as follows, let's say for example we have a class Fridge that contains a Set and our view needs to react to changes in the latter despite not having any subviews that observe each item.

    class Food: ObservableObject, Hashable {
        @Published var name: String
        @Published var calories: Float
        
        init(name: String, calories: Float) {
            self.name = name
            self.calories = calories
        }
        
        static func ==(lhs: Food, rhs: Food) -> Bool {
            return lhs.name == rhs.name && lhs.calories == rhs.calories
        }
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(self.name)
            hasher.combine(self.calories)
        }
    }
    
    class Fridge: ObservableObject {
        @Published var food: Set
        
        init(food: Set) {
            self.food = food
        }
    }
    
    struct FridgeCaloriesView: View {
        @ObservedObjectCollection var food: AnyCollection
    
        init(fridge: Fridge) {
            self._food = ObservedObjectCollection(wrappedValue: fridge.food)
        }
    
        var totalCalories: Float {
            self.food.map { $0.calories }.reduce(0, +)
        }
    
        var body: some View {
            Text("Total calories in fridge: \(totalCalories)")
        }
    }
    

提交回复
热议问题