Hey, hi, hello! Today we're diving deep into the observer design pattern and how Swift and SwiftUI have brought a fresh take to this classic concept with Observables.
Observables are a new way to manage and respond to data changes in your app, providing more flexibility and type safety than ever before. Say goodbye to @Published and @ObservableObject and let's embrace Observables.
Observation supports subclassing of observable types. If you have an observable class that supports subclassing, don’t include the `final` keyword in the class declaration. Including `final` prevents subclassing of the type.
Note, you need to `import Observation` to use this functionality.
Creating an Observable Object
Creating an observable type is incredibly simple in Swift. All it requires is attaching the @Observable macro to the type declaration. This macro takes care of everything needed to make the type observable, from declaring to implementing the Observable protocol. It does all of this during compile time, ensuring your runtime performance isn't impacted.
In our workout example, let's consider the Workout class:
@Observable class Workout: Identifiable {
var name: String = "Default Workout"
var duration: Double = 30.0
var intensity: String = "Moderate"
var inProgress: Bool = false
}
In this example, our Workout class is now observable, meaning any changes in its properties, such as name, duration, or intensity, can be tracked and acted upon.
With our observable Workout class in place, the Observation framework allows us to:
1. Identify the Workout class as an observable type.
2. Keep an eye on alterations within any Workout instance.
3. Use these changes to trigger actions elsewhere, such as updating an app’s user interface.
The Observation framework handles the hard work of managing observers and notifications, allowing us to focus on reacting to the changes that matter in our application.
Bringing Observables into SwiftUI
SwiftUI's reactive nature works harmoniously with Swift's Observation framework. This potent combination allows for seamless tracking and reaction to changes in our app's state, directly influencing the UI. Let's see how this works in our workout scenario.
Consider we have a SwiftUI View that displays our workout details. We can make this View reactive to changes in an Observable object.
struct WorkoutView: View {
var workout: Workout
var body: some View {
VStack {
Text("Workout Name: \(workout.name)")
Text("Workout Duration: \(workout.duration) minutes")
}
}
}
Here, WorkoutView establishes a dependency on the workout instance of our Observable Workout class. Whenever the workout instance undergoes a change in its properties (name, duration, or intensity), WorkoutView automatically reflects these changes in the UI.
Note that here we have nothing in the view that observed the `intensity` property, so if any changes occur to the workout’s intensity, the view will not update.
In order to make changes to the view, we’ll add a Button that toggles the workout state to “in progress”.
struct WorkoutView: View {
var workout: Workout
var body: some View {
VStack {
Text("Workout Name: \(workout.name)")
Text("Workout Duration: \(workout.duration) minutes")
Button {
workout.inProgress.toggle()
} label: {
Text(workout.inProgress ? "End Workout" : "Start Workout")
}
}
}
}
Now that we’ve added a way to change the state of a property in out Observable object, out view knows to track changes to this property and update the view accordingly, without needing to add the @Published property wrapper.
With the power of Observation, we can track changes in computed properties that use another property in the class. Let's demonstrate this by adding a computed property status to the Workout class. The status will be a string that tells us whether the workout is in progress or not and how long the workout will last.
Here's how we can update our Workout class:
@Observable class Workout: Identifiable {
var name: String = "Default Workout"
var duration: Double = 30.0
var intensity: String = "Moderate"
var inProgress: Bool = false
var status: String {
if inProgress {
return "Workout in progress. Duration: \(duration) minutes."
} else {
return "Workout not started."
}
}
}
The status property is computed based on the inProgress property and also incorporates the duration property. When inProgress is true, it returns a string indicating the workout is in progress and shows the duration of the workout. If inProgress is false, it simply states that the workout has not started.
Now, let's incorporate this into our WorkoutView:
struct WorkoutView: View {
var workout: Workout
var body: some View {
VStack {
Text("Workout Name: \(workout.name)")
Text("Workout Duration: \(workout.duration) minutes")
Text("Workout Intensity: \(workout.intensity)")
Text(workout.status)
Button {
workout.inProgress.toggle()
} label: {
Text(workout.inProgress ? "End Workout" : "Start Workout")
}
}
}
/* Code to update workout duration */
}
We've added Text(workout.status) to display the status of the workout. Also, in the button action, we've added an operation to increase the duration of the workout by 10 minutes each time the workout starts.
Now, when you toggle the workout's inProgress state, SwiftUI will not only update the button label, but also the status text. This is because the status computed property depends on inProgress and duration, both of which SwiftUI is observing for changes.
Mastering Swift's Observable objects is crucial for SwiftUI development. They provide a powerful and flexible way to track state changes in your app and to keep your UI up-to-date with your data.
In this post, we've delved into creating an Observable object and using it within SwiftUI views. We've seen how changes to the Observable object's properties can be tracked and reflected in the UI. We've also explored how computed properties can be observed and how these changes affect our SwiftUI views.
The ability to correctly use and manage Observable objects in SwiftUI is an essential skill for any Swift developer. Keep experimenting and keep learning - I’d love to know how you use Observables in your projects.