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

swiftui view not updating
swiftui force view update
swiftui array
swiftui environmentobject array
swiftui pass observed object
swiftui list array
swiftui observedobject vs objectbinding
swiftui nested observableobject

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 View. However, if I change the value of an existing Person, it is not reloaded in the View

//  NamesClass.swift
import Foundation
import SwiftUI
import Combine

class Person: ObservableObject,Identifiable{
    var id: Int
    @Published var name: String

    init(id: Int, name: String){
        self.id = id
        self.name = name
    }

}

class People: ObservableObject{
    @Published var people: [Person]

    init(){
        self.people = [
            Person(id: 1, name:"Javier"),
            Person(id: 2, name:"Juan"),
            Person(id: 3, name:"Pedro"),
            Person(id: 4, name:"Luis")]
    }

}
struct ContentView: View {
    @ObservedObject var mypeople: People

    var body: some View {
        VStack{
            ForEach(mypeople.people){ person in
                Text("\(person.name)")
            }
            Button(action: {
                self.mypeople.people[0].name="Jaime"
                //self.mypeople.people.append(Person(id: 5, name: "John"))
            }) {
                Text("Add/Change name")
            }
        }
    }
}

If I uncomment the line to add a new Person (John), the name of Jaime is shown properly. However if I just change the name, this is not shown in the View.

I'm afraid I'm doing something wrong or maybe I don't get how the ObservedObjects work with Arrays.

Any help or explanation is welcome!


Person is a class, so it is a reference type. When it changes, the People array remains unchanged and so nothing is emitted by the subject. However, you can manually call it, to let it know:

Button(action: {
    self.mypeople.objectWillChange.send()
    self.mypeople.people[0].name="Jaime"    
}) {
    Text("Add/Change name")
}

Alternatively (and preferably), you can use a struct instead of a class. And you do not need to conform to ObservableObject, nor call .send() manually:

import Foundation
import SwiftUI
import Combine

struct Person: Identifiable{
    var id: Int
    var name: String

    init(id: Int, name: String){
        self.id = id
        self.name = name
    }

}

class People: ObservableObject{
    @Published var people: [Person]

    init(){
        self.people = [
            Person(id: 1, name:"Javier"),
            Person(id: 2, name:"Juan"),
            Person(id: 3, name:"Pedro"),
            Person(id: 4, name:"Luis")]
    }

}

struct ContentView: View {
    @ObservedObject var mypeople: People = People()

    var body: some View {
        VStack{
            ForEach(mypeople.people){ person in
                Text("\(person.name)")
            }
            Button(action: {
                self.mypeople.people[0].name="Jaime"
            }) {
                Text("Add/Change name")
            }
        }
    }
}

How to send state updates manually using objectWillChange, How to use @ObservedObject to manage state from external objects Updated for Xcode 12.0 Publishers are Combine's way of announcing changes to whatever is watching, which in the case of SwiftUI is zero or more views. AirDrop, iBeacon, iPhone, iPad, Safari, App Store, watchOS, tvOS, Mac and  I found a lot of SwiftUI-related topics about this which didn't help (eg Why an ObservedObject array is not updated in my SwiftUI application?) This doesn't work with Combine in Swift (specifically not using SwiftUI):


I think there is a more elegant solution to this problem. Instead of trying to propagate the objectWillChange message up the model hierarchy, you can create a custom view for the list rows so each item is an @ObservedObject:

struct PersonRow: View {
    @ObservedObject var person: Person

    var body: some View {
        Text(person.name)
    }
}

struct ContentView: View {
    @ObservedObject var mypeople: People

    var body: some View {
        VStack{
            ForEach(mypeople.people){ person in
                PersonRow(person: person)
            }
            Button(action: {
                self.mypeople.people[0].name="Jaime"
                //self.mypeople.people.append(Person(id: 5, name: "John"))
            }) {
                Text("Add/Change name")
            }
        }
    }
}

In general, creating a custom view for the items in a List/ForEach allows each item in the collection to be monitored for changes.

@ObservedObject Issue, @ObservedObject Issue The SwiftUI Apple tutorial is using @ EnvironmentObject instead of @ObservedObject. Can anyone confirm that most folks do not have this issue? amount of making changes and I notice there is no xcode update to go with the Beta? Certificates, Identifiers & Profiles · App Store Connect. This class receives and send network requests updating the timers array struct ScheduleTimer: Identifiable { var id: Int var name: String @State var start: Date @State var end: Dat


For those who might find it helpful. This is a more generic approach to @kontiki 's answer.

This way you will not have to be repeating yourself for different model class types

import Foundation
import Combine
import SwiftUI

class ObservableArray<T>: ObservableObject {

    @Published var array:[T] = []
    var cancellables = [AnyCancellable]()

    init(array: [T]) {
        self.array = array

    }

    func observeChildrenChanges<T: ObservableObject>() -> ObservableArray<T> {
        let array2 = array as! [T]
        array2.forEach({
            let c = $0.objectWillChange.sink(receiveValue: { _ in self.objectWillChange.send() })

            // Important: You have to keep the returned value allocated,
            // otherwise the sink subscription gets cancelled
            self.cancellables.append(c)
        })
        return self as! ObservableArray<T>
    }


}

class Person: ObservableObject,Identifiable{
    var id: Int
    @Published var name: String

    init(id: Int, name: String){
        self.id = id
        self.name = name
    }

} 

struct ContentView : View {
    //For observing changes to the array only. 
    //No need for model class(in this case Person) to conform to ObservabeObject protocol
    @ObservedObject var mypeople: ObservableArray<Person> = ObservableArray(array: [
            Person(id: 1, name:"Javier"),
            Person(id: 2, name:"Juan"),
            Person(id: 3, name:"Pedro"),
            Person(id: 4, name:"Luis")])

    //For observing changes to the array and changes inside its children
    //Note: The model class(in this case Person) must conform to ObservableObject protocol
    @ObservedObject var mypeople: ObservableArray<Person> = try! ObservableArray(array: [
            Person(id: 1, name:"Javier"),
            Person(id: 2, name:"Juan"),
            Person(id: 3, name:"Pedro"),
            Person(id: 4, name:"Luis")]).observeChildrenChanges()

    var body: some View {
        VStack{
            ForEach(mypeople.array){ person in
                Text("\(person.name)")
            }
            Button(action: {
                self.mypeople.array[0].name="Jaime"
                //self.mypeople.people.append(Person(id: 5, name: "John"))
            }) {
                Text("Add/Change name")
            }
        }
    }
}

SwiftUI changes in data, how to handle : swift, I've got two screens and one array of structs. And going back to the main screen does not, in fact, update the personList. import SwiftUI struct Person: Codable { let name: String var isPresent = false { didSet { self.save() } } private func save() Yeah, I did that as well, because of the need to continue coding the app. The reason - this simply won’t compile. body is immutable, so we’re not allowed to set prevValue in it. There is a workaround to this problem - we can create a reference type wrapper for storing the prevValue, but this all starts to be really cumbersome and smelly.


User kontiki, Does not conform to protocol BindableObject - Xcode 11 Beta 4 · stackoverflow. com. 58 · Why an ObservedObject array is not updated in my SwiftUI application? SwiftUI and How NOT to Initialize Bindable Objects. ObservedObject, and Published. While the “object’s data has changed” mechanism has been updated, the premature initialization issue


How to modify state from state in SwiftUI - Fantageek, This is similar to the warning when we change state inside render in React / 57459727/why-an-observedobject-array-is-not-updated-in-my-swiftui-application   SwiftUI keeps @State property in a separate memory place to preserve it during many reload cycles. @Observabled is meant for sharing reference objects across views To to use @State we should use struct, and to use onReceive we should introduce another Publisher like imagePublisher


Why I quit using the ObservableObject, This means if we're building a large SwiftUI app based on a Redux-like mutating state objects, such as EnvironmentObject or ObservedObject . The == func always returns true , telling SwiftUI that our view does not require re- calculation. In the == func we are comparing the prevValue to the updated  SwiftUI keeps @State property in a separate memory place to preserve it during many reload cycles. @Observabled is meant for sharing reference objects across views. To to use @State we should use struct, and to use onReceive we should introduce another Publisher like imagePublisher