SwiftUI
ObservedObject
Array
State Management
Swift Programming

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

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

This issue usually appears when a SwiftUI view holds an array of reference-type objects and you mutate one object's properties. The array itself did not change, so the parent publisher may not emit the kind of update you expected, and the UI can appear stale even though one inner object changed.

The fix is usually not "force the array to refresh." The fix is to make sure the right object is being observed at the right level. In SwiftUI, that often means each row view should observe its own item directly.

Why Publishing the Array Is Not Enough

Consider this model:

swift
1import Combine
2
3final class Person: ObservableObject, Identifiable {
4    let id = UUID()
5    @Published var name: String
6
7    init(name: String) {
8        self.name = name
9    }
10}
11
12final class PeopleStore: ObservableObject {
13    @Published var people: [Person] = [
14        Person(name: "Ada"),
15        Person(name: "Grace")
16    ]
17}

If you change people[0].name, the Person publishes a change. But people as an array value was not replaced, so PeopleStore itself may not emit the kind of update your parent view is relying on.

That is the core mismatch: the array is published, but the mutation happened inside one referenced object.

Observe Each Item at the Row Level

The common fix is to let each row observe the object it renders:

swift
1import SwiftUI
2
3struct PeopleView: View {
4    @StateObject private var store = PeopleStore()
5
6    var body: some View {
7        List {
8            ForEach(store.people) { person in
9                PersonRow(person: person)
10            }
11        }
12    }
13}
14
15struct PersonRow: View {
16    @ObservedObject var person: Person
17
18    var body: some View {
19        Text(person.name)
20    }
21}

Now when person.name changes, PersonRow updates because it is observing that specific object directly. This is the normal SwiftUI pattern for arrays of ObservableObject items.

Value Types Behave Differently

If your array holds value types such as structs instead of reference types, updates behave more naturally with SwiftUI because changing one element changes the array value:

swift
1struct PersonValue: Identifiable {
2    let id = UUID()
3    var name: String
4}
5
6final class ValueStore: ObservableObject {
7    @Published var people: [PersonValue] = [
8        PersonValue(name: "Ada"),
9        PersonValue(name: "Grace")
10    ]
11}

With structs, mutations usually pass through the published array more cleanly because the array itself changes.

That does not mean reference types are wrong. It means you need to be more deliberate about the observation boundaries.

Use the Right Ownership Wrapper

Another common source of stale updates is wrapper choice. The owner of the model object should usually create it with @StateObject, while child views that receive it should use @ObservedObject.

If the parent creates the object with @ObservedObject, the object may be recreated unexpectedly as the view hierarchy refreshes. That can make updates look inconsistent even when the publishing model is otherwise correct.

Manual Publishing Is Usually a Smell

You can force the parent object to publish before mutating an inner object:

swift
1final class PeopleStore: ObservableObject {
2    @Published var people: [Person] = []
3
4    func renameFirstPerson(to name: String) {
5        objectWillChange.send()
6        people[0].name = name
7    }
8}

This works, but it is usually a sign that the observation structure is too coarse. Row-level observation or value-type modeling is often cleaner than sending manual refresh signals from the container.

Common Pitfalls

The biggest mistake is expecting @Published var items: [SomeClass] to behave the same way as @Published var items: [SomeStruct]. Reference types and value types interact with SwiftUI updates differently.

Another common issue is observing only the store and not the row items. If the row depends on item-level mutation, the row should usually observe the item.

People also misuse @ObservedObject and @StateObject. The owner should keep stable object identity with @StateObject, while children observe an existing object.

Finally, manual objectWillChange.send() can patch symptoms, but it often hides a better design that would make the update path explicit.

Summary

  • A published array of reference types does not automatically behave like a published array of value types.
  • Mutating a property inside one ObservableObject item is different from replacing the array.
  • Let row views observe each item directly when you render arrays of ObservableObject.
  • Use @StateObject for owners and @ObservedObject for consumers of existing objects.
  • Treat manual publishing as a fallback, not as the first design choice.

Course illustration
Course illustration

All Rights Reserved.