The Gotcha of Mixing SwiftUI's @Observable with Core Data

August 27, 2026

Learn why your SwiftUI views might stop updating when mixing the modern @Observable macro with Core Data NSManagedObjects, and how to fix it.

The Gotcha of Mixing SwiftUI's @Observable with Core Data

If you are building modern iOS apps, you are likely adopting Apple's new @Observable macro to power your state management. It's incredibly fast, requires less boilerplate than the old ObservableObject, and feels like magic.

However, if you are mixing @Observable view models with a legacy Core Data stack, you might eventually run into a frustrating bug: Your UI stops updating when you change your data.

Let's look at why this happens and how to fix it.

Imagine we have an @Observable manager class that uses an NSFetchedResultsController to fetch a list of Movie records from Core Data.

import SwiftUI
import CoreData

@Observable
class MovieManager: NSObject {
    var movies: [Movie] = []

    // It has NSManagedObjectContext which helps fetch, update and save movies.
}

In our view, we iterate over these movies and pass them into a child view to display and update their rating:

struct ContentView: View {
    @Environment(MovieManager.self) private var manager

    var body: some View {
        List(manager.movies, id: \.id) { movie in
            RatingView(movie: movie)
        }
    }
}

This is our RatingView where the user taps to change the rating:

struct RatingView: View {
    @Environment(MovieManager.self) private var manager

    var movie: Movie

    var body: some View {
        HStack {
            Text(movie.name)
            Button("Increment Rating") {
                movie.rating += 1
                // saving context
                manager.save()
            }
        }
    }
}

The Problem

When you tap the "Increment Rating" button, the Core Data context saves, the database updates, and your MovieManager successfully fetches the fresh data.

But your RatingView does not visually update.

The "Why"

This bug is a perfect storm of three separate framework behaviors colliding:

  1. NSManagedObject is a Reference Type: The movie variable in RatingView is just a pointer to a class in memory. When you update movie.rating, the object itself hasn't changed its address.
  2. SwiftUI's Diffing Engine: When your MovieManager fetches the updated list of movies, it hands ContentView an array of the exact same object references with the exact same ids. SwiftUI looks at the List, sees that the IDs haven't changed, and decides it doesn't need to redraw the rows.
  3. Observation Mismatch: The new @Observable macro uses property tracking. But NSManagedObject does not use the @Observable macro—it relies on the older Combine ObservableObject protocol. Because of this, SwiftUI's modern observation engine has no idea that the properties inside the movie object have changed.

The Solution

The fix is incredibly simple but requires you to remember that Core Data objects still live in the Combine world.

Since NSManagedObject conforms to ObservableObject out of the box, we just need to tell SwiftUI to observe it using the older @ObservedObject property wrapper inside our child view:

struct RatingView: View {
    // 👇 The Magic Fix
    @ObservedObject var movie: Movie

    var body: some View {
        // ...
    }
}

By marking the Movie as an @ObservedObject, we explicitly tell SwiftUI to listen to the publishers that Core Data automatically synthesizes for its managed properties.

Now, the moment movie.rating changes, RatingView receives the event, invalidates its body, and re-renders perfectly!

Summary

When mixing the new Observation framework with Core Data, remember that:

  • Your managers/controllers can happily use @Observable.
  • But your NSManagedObject instances must be observed using @ObservedObject when passed into child views.