New Observation in SwiftUI
SwiftUI new data flow in iOS 17.
In the latest version of SwiftUI (iOS 17), there is an exciting new feature called the @observable
macro, which offers a cleaner and more efficient way to handle UI updates. Let's explore how it works by converting an existing code example.
import SwiftUI
class Car: ObservableObject {
@Published var name: String
@Published var speed: Int
init(name: String, speed: Int) {
self.name = name
self.speed = speed
}
}
struct ContentView: View {
@StateObject var car = Car(name: "BMW", speed: 100)
var body: some View {
VStack {
Text("\(car.name)")
.bold()
.font(.title)
Text(" \(car.speed)")
.font(.title2)
Button{
car.speed += 1
} label: {
Text("Add")
}
.buttonStyle(.borderedProminent)
.padding(.top)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Currently, when we want to update our UI based on changes in our data model, we conform to the ObservableObject
protocol. We mark any property we want to observe with @Published
, which tells SwiftUI to update the UI whenever that property changes. In our example, we have a Car
data model, and whenever the speed
property changes, the UI updates because it is marked with @Published
. Additionally, we use @StateObject
or @ObservedObject
wrappers in our views to indicate that we want to observe changes to the Car
object.
To demonstrate the new way of updating the UI, we introduce the @observable
macro. We import the Observation
module and replace the ObservableObject
conformance with the @observable
macro. This simple change eliminates the need for the @Published
annotations on individual properties. Instead, all properties within the @observable
class are automatically observed.
import SwiftUI
import Observation
@Observable class Car{
var name: String = ""
var speed: Int = 100
init(name: String, speed: Int) {
self.name = name
self.speed = speed
}
}
struct ContentView: View {
var car = Car(name: "BMW", speed: 100)
var body: some View {
VStack {
Text("\(car.name)")
.bold()
.font(.title)
Text(" \(car.speed)")
.font(.title2)
Button{
car.speed += 1
} label: {
Text("Add")
}
.buttonStyle(.borderedProminent)
.padding(.top)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In the updated code, our data model is a Car
class with properties like name
and speed
. We mark the class with @observable
and provide initial values for its properties to satisfy the requirements of the new macro. The @Published
annotations are no longer needed, as all properties are now observed by default.
In our SwiftUI view, instead of using @StateObject
or @ObservedObject
, we declare a @var
property called car
of type Car
. This property is automatically observed, simplifying the code and eliminating the need to choose between @StateObject
and @ObservedObject
.
The @observable
macro offers more than just cleaner syntax. Under the hood, SwiftUI tracks the views that access properties of an observable class, enabling more efficient UI updates. By eliminating unnecessary UI updates, SwiftUI can deliver better performance. This tracking allows SwiftUI to be more precise and efficient in updating only the necessary parts of the UI.
One additional benefit of using macros, such as @observable
, is that they abstract away repetitive and boilerplate code. While the inner workings of the macro may seem like magic, Xcode 15 beta 2 introduces a feature where you can expand the macro and see the generated code. This visibility into the generated code can help debug any issues and understand the underlying mechanics, to check it right click on @observable
then hit “Expand Macro”.
In summary, adopting the @observable
macro in SwiftUI brings cleaner syntax and improved performance to UI updates. By leveraging the macro, SwiftUI tracks views accessing properties of observable objects, reducing unnecessary UI updates and resulting in better performance. This update is a significant improvement for SwiftUI developers.