Migrating to the Observation framework in SwiftUI
Learn how to use SwiftUI's @Observable macro.
02 Feb 2026 · 5 min read
SwiftUI's current state-of-the-art approach to state observation is the Observation framework. It uses the macro system in Swift to transform Swift types into observable state.
In this article, we'll look at how Observation differs from the older ObservableObject and @Published pattern, and how to migrate existing SwiftUI code step by step.

How Observation differs conceptually
Observation isn't just a syntax change, it introduces property-level tracking.
Unlike ObservableObject, which broadcasts change notifications for the entire object, Observation tracks access to individual properties. SwiftUI only updates views when a property they actually read changes.
This reduces unnecessary updates and makes view invalidation more precise.
Migration steps
Observation comes with a @Observable macro which automatically adds observation support to a Swift type. To start using it, the following migration steps are required:
- Replace ObservableObject with @Observable macro
- Replace @StateObject with @State
- Replace @ObservedObject with @Bindable
- Replace @EnvironmentObject with @Environment
Not all projects will need every step, but these changes cover the most common migration paths from ObservableObject-based code.
Let's look at these steps in more detail.
1. Replace ObservableObject with @Observable macro
To replace ObservableObject with the @Observable macro, we need to remove conformances to ObservableObject, remove all @Published annotations, add @ObservationIgnored where needed and apply the Observable macro instead.
Here is an example:
/// BEFOREclass UserViewModel: ObservableObject {@Published var name: String?@Published var email: String?var authenticationService: AuthenticationService}/// AFTER@Observable class UserViewModel {var name: String? = nilvar email: String? = nil@ObservationIgnoredvar authenticationService: AuthenticationService}
Properties marked with @ObservationIgnored are excluded from observation and do not trigger view updates. This is an important migration detail that may easily be missed.
2. Replace @StateObject with @State
Before the Observation framework, we used @State for value types and @StateObject for reference types to let SwiftUI update views whenever changes occured. With Observation, both types are covered by @State:
/// BEFOREstruct UserView: View {@StateObject var viewModel = UserViewModel()var body: some View {...}}// AFTERstruct UserView: View {@State var viewModel = UserViewModel()var body: some View {...}}
3. Replace @ObservedObject with @Bindable
Before the Observation framework, we used @Binding for value types and @ObservedObject for reference types to create two-way bindings. With Observation, we use @Binding for value types and @Bindable for reference types:
// BEFOREstruct UserView: View {@ObservedObject var viewModel: UserViewModelvar body: some View {...}}// AFTERstruct UserView: View {@Bindable var viewModel: UserViewModelvar body: some View {...}}
If you want to better understand when and why @Bindable is required, check out this article on how @Bindable works in SwiftUI.
4. Replace @EnvironmentObject with @Environment
Instead of the @EnvironmentObject property wrapper, the Observation framework uses the @Environment property wrapper to pass objects down the view hierarchy:
// BEFOREstruct UserView: View {@EnvironmentObject var viewModel: UserViewModelvar body: some View {...}}// AFTERstruct UserView: View {@Environment(UserViewModel.self) private var viewModelvar body: some View {...}}
Migrating incrementally and understanding these conceptual changes helps avoid subtle issues and keeps SwiftUI state predictable as projects evolve.



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



