SwiftUI Tip: Avoid Nesting ObservableObjects
A problem many SwiftUI developers have is their views not updating when the data changes. A common reason for this problem is having nested ObservableObjects in their code.
In this article you will learn what a nested ObservableObject is, why nested ObservableObjects cause problems, and how you can get your views to update correctly.
What is a Nested ObservableObject?
A nested ObservableObject occurs when you have a class that conforms to ObservableObject
and has a property whose class also conforms to ObservableObject
.
Let’s look at an example of a nested ObservableObject. Suppose you have an issue tracking app. The data model for the app includes projects and issues. Each project can have multiple issues.
If you make Project
a class, have it conform to ObservableObject
, and give it a list of issues,
class Project: ObservableObject {
@Published var issues: [Issue]
...
}
And the Issue
class conforms to ObservableObject
,
class Issue: ObservableObject {
...
}
You have a nested ObservableObject. Issue
is nested inside Project
.
What’s Wrong with Nested ObservableObjects?
Why do nested ObservableObjects cause problems? When a change occurs in the inner object, SwiftUI does not automatically report the change to the outer object. This means the view for the outer object will not update when the data in the inner object changes.
In the issue tracking app example, the project does not get notified of any changes to its issues. When an issue changes, the project’s view will not update to reflect the changes.
Fixing the Problem
At this point you’re wondering what you have to do to fix the problem and get your views to update correctly. The following techniques can fix the problem:
- Use structs instead of classes for your data models.
- Use the Observation framework.
- Manually send a message when there is a change in the inner object.
Use Structs Instead of Classes
Using structs for your data models can be the easiest fix. Structs do not use the ObservableObject
protocol so you won’t get nested ObservableObjects. In the issue tracker example, making Project
or Issue
a struct will eliminate the nested ObservableObject.
But you might find that parts of your app’s data model must be classes. When I was developing Bartleby, I initially tried using a struct for book chapters, but the chapter text would not save when I switched chapters. The only way to get the chapter text to save was to make Chapter
a class. You may have a similar situation in your app.
Remember you don’t have to change every class in your app to a struct. Make sure you don’t have one class nested inside another class.
Use the Observation Framework
Apple added the Observation framework in iOS 17 and macOS 14. If you use the Observation framework for your data and to pass data to views, you will fix the problem.
The downside of using Observation is you can’t use it in earlier iOS and macOS versions. If you need to support iOS 16 or earlier in your app, you can’t use the Observation framework.
Manual Changes
If you can’t use structs for your data model, can’t avoid nesting ObservableObjects, and can’t use the Observation framework, you have one more workaround. Manually sending a message when the data changes.
Call the objectWillChange.send
function to manually send a message when the data changes. The function takes no arguments.