Intro to Observation in SwiftUI

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

So far, you’ve learned how to manage state with struct values in @State and @Binding properties. While effective for many situations, you’ll sometimes need to store state in class objects. This section introduces managing state in SwiftUI using class.

If you’re unsure about the difference between struct and class, do a quick search and read up on value types and reference types. A high-level understanding of this will help you in this lesson.

When you store state in a class object in SwiftUI, you must make the class observable. Unlike value types like struct and enum, SwiftUI can’t detect changes in class properties automatically. This is why classes must be observable when storing state in SwiftUI, allowing SwiftUI to update a view’s body when a class object’s property changes.

You can hold class state objects in @State, @Bindable, @Environment, and plain var properties. Most often, you’ll use var, @Bindable, and @Environment properties. In this lesson, you’ll focus on using objects with @Bindable properties. If this is your first encounter with @Bindable, don’t worry; this lesson will introduce and explain its use and benefits.

Now that you know storing state in classes might be necessary, it’s time to learn how to make these classes observable.

Making a Class Observable

To make a class observable, use the @Observable macro from the Observation framework:

@Observable
final class MyViewsState {
  var title: String
  // ...
}

When to Use Class for State Management

Several situations call for @Observable class objects. You should start by building with structs for state and switch to classes when your needs exceed what structs can provide. Since making this switch depends on specific cases, detailing all scenarios requiring classes for state management is outside this lesson’s scope. However, you’ll tackle one very common situation next.

Editing Financial Entries

One common use case for classes is the list and list detail pattern, typical in iOS, where users move from a list of objects to a detail screen for an item and modify it. Changes to an item need to be reflected in the list when users return from the detail screen.

Starter Project

To get started, open the starter Xcode project located at 03-leveraging-observation-for-shared-state-management/02-instruction/Starter/MyBudget.xcodeproj.

Step 1: Making FinancialEntry Mutable

Currently, FinancialEntry is a struct with immutable properties. To allow editing, you need to make its properties mutable. This step is essential because mutable properties can be updated in the EditFinancialEntryView.

var amount: Double
var category: String
var isExpense: Bool

Step 2: Preparing EditFinancialEntryView for Editing

You need to pass a FinancialEntry instance to EditFinancialEntryView for editing. This step sets up the view to accept an entry.

  var entry: FinancialEntry
#Preview {
  EditFinancialEntryView(entry:
    FinancialEntry(
      id: UUID(),
      amount: 100,
      category: "Groceries",
      isExpense: true
    ))
}

Step 3: Linking Entries to EditFinancialEntryView

To navigate from a financial entry in the list to its edit view, you’ll implement the destination of NavigationLink in ContentView.

EditFinancialEntryView(entry: entry)
TextField("Amount", value: $entry.amount, format: .number)

Why Is Class Required Here?

Recall from Lesson 2 that bindings are created from @State properties. The list view holds a @State property of an array of struct entries. Based on what you’ve learned, you might expect to pass a binding to one of the array’s entries into the detail view, allowing it to modify the entry. However, creating a binding to a single entry within an array isn’t possible in SwiftUI.

Step 4: Transitioning to a Class-Based Model With @Observable

Convert FinancialEntry to a class, and make it observable using the @Observable macro. This allows SwiftUI to track and react to changes in the class’s properties.

final class FinancialEntry: Identifiable {
init(id: UUID, amount: Double, category: String, isExpense: Bool) {
  self.id = id
  self.amount = amount
  self.category = category
  self.isExpense = isExpense
}
@Observable
final class FinancialEntry: Identifiable {

What Is @Bindable and Why Do You Need It?

What’s going on here? Why can’t you create a binding for the FinancialEntry‘s amount property using $entry.amount? While making a class observable with @Observable allows SwiftUI to detect and respond to changes in an object’s properties, @Observable doesn’t enable you to create bindings for those properties. @Observable is designed solely for SwiftUI to track changes, which is why the project doesn’t build successfully yet, even though you’ve switched to using a class marked as @Observable.

TextField("Amount", value: $entry.amount, format: .number)

Step 5: Enabling Bindings With @Bindable

Open EditFinancialEntryView.swift, and add the @Bindable property wrapper to the entry property:

@Bindable var entry: FinancialEntry

Step 6: Completing the Edit Form With Proper Data Binding

Now that FinancialEntry is observable and bindable, it’s time to complete the edit form.

.keyboardType(.decimalPad)
TextField("Category", text: $entry.category)
Toggle(isOn: $entry.isExpense) {
  Text("Is Expense")
}

@Binding Versus @Bindable

Learning about @Bindable right after @Binding might seem confusing. Here’s a clear overview to help you understand when to use @Bindable:

Learning More About @Observable Objects

Not only can you store and pass @Observable object instances using view properties, but you can also store them in SwiftUI’s environment. However, learning how to do this is beyond the scope of this lesson. To learn more about using Observation in SwiftUI, visit Apple’s Managing model data in your app sample code and documentation.

Note on Observation in iOS 17

This lesson focuses on state management using Observation, which is available in iOS 17 and above. It’s important to note that this lesson doesn’t cover prior class-type state management concepts such as @ObservableObject, @ObservedObject, @StateObject, and @EnvironmentObject. As you progress in your learning journey, you may encounter different state management approaches used in earlier versions of iOS. However, this lesson is tailored to provide you with an understanding of only the latest practices in state management with SwiftUI.

Demo: Separating Logic Outside of SwiftUI

You just saw how to use Observation to display and modify objects in a detail view from a list. Observation also facilitates the separation of logic from your SwiftUI views, which simplifies unit testing. In the upcoming video demo, you’ll implement logic in a new observable class to calculate the totals for expense and income items and display these totals in a new section in the list view.

See forum comments
Download course materials from Github
Previous: Introduction Next: Extracting Logic Outside SwiftUI Using Observation Demo