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
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)")
}
}