Logo for tanaschita.com

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.

Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE

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:

  1. Replace ObservableObject with @Observable macro
  2. Replace @StateObject with @State
  3. Replace @ObservedObject with @Bindable
  4. 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:

/// BEFORE
class UserViewModel: ObservableObject {
@Published var name: String?
@Published var email: String?
var authenticationService: AuthenticationService
}
/// AFTER
@Observable class UserViewModel {
var name: String? = nil
var email: String? = nil
@ObservationIgnored
var 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:

/// BEFORE
struct UserView: View {
@StateObject var viewModel = UserViewModel()
var body: some View {
...
}
}
// AFTER
struct 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:

// BEFORE
struct UserView: View {
@ObservedObject var viewModel: UserViewModel
var body: some View {
...
}
}
// AFTER
struct UserView: View {
@Bindable var viewModel: UserViewModel
var 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:

// BEFORE
struct UserView: View {
@EnvironmentObject var viewModel: UserViewModel
var body: some View {
...
}
}
// AFTER
struct UserView: View {
@Environment(UserViewModel.self) private var viewModel
var body: some View {
...
}
}

Migrating incrementally and understanding these conceptual changes helps avoid subtle issues and keeps SwiftUI state predictable as projects evolve.

Sponsorship logo
Preparing for a technical iOS job interview
Preparing for a technical iOS Job Interview with over 300 questions & answers. Covering Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, iOS File System, Core Data, Concurrency with async/await, Security, Automated Testing, Dependency Management, AI & Machine Learning and more.
LEARN MORE
Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE
Sponsorship logo
Become a sponsor of tanaschita.com
By publishing an article on different iOS topics every week, tanaschita.com is constantly growing in the developer community and may provide a great audience for you as a sponsor.
CLICK TO LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

observation

swiftui

swift

ios

How to manually subscribe to changes of SwiftUI's @Observable objects

Learn how to use the withObservationTracking method of the Observation framework.

22 Aug 2023 · 3 min read

Latest articles and tips

© 2026 tanaschita.com

Privacy policy

Impressum