In the following example, I'm trying to display a list of objects.
Object
has a title that changes from "old title" to "new title" asynchronously in the init()
AllObjects
holds an array of Object
s and asynchronously adds them in the init()
ContentView
displays the title of each object
class Object: ObservableObject {
@Published var title: String = "old title"
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.title = "new title"
}
}
}
class AllObjects: ObservableObject {
@Published var allObjects: [Object] = []
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.allObjects.append(Object())
}
}
}
struct ContentView: View {
@StateObject var allObjects = AllObjects()
var body: some View {
VStack {
ForEach(allObjects.allObjects, id: \.title) { obj in
Text(obj.title)
}
}
}
}
I'm trying to have the "new title" displayed, but it isn't updating the UI when the title is changed. Why is this happening, and how can I fix it?
Your problem is because changes in Object are not forwarded to AllObjects. Therefore the observedObject doesn't receive any changes and the view stay still.
To fix that you can listen to changes on object and forward them to AllObject like that :
class AllObjects: ObservableObject {
@Published var allObjects: [Object] = []
var objectStorage: [AnyCancellable] = []
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let self else {return}
let object = Object()
object.objectWillChange.sink {
self.objectWillChange.send()
}.store(in: &self.objectStorage)
self.allObjects.append(object)
}
}
}
[Edit] here is an explanation why your code doesn't work :
allObjects
is an array of object, so it holds reference to the object.
allObject
triggers an update when you add, remove, or switch objects from it, but when one object content change, the array doesn't, since the reference didn't change.
It's the value that changed, not the address. Thus no update of the array