DEV Community

MZ
MZ

Posted on

Observables in SwiftUI

Observables

This article aims to explain how observables help or fit in into the use case of sharing data among views.

Before that, we need some context.

When we want to share data among views, we usually use classes instead of structs. The main reason is that whenever we use structs, each view will have its own copy of said struct, so it does not really fit the use case if you would like to share data among views. To be able to share data among views, you need to use classes.

import Combine

class Counter: ObservableObject {
    @Published var count = 0
}
Enter fullscreen mode Exit fullscreen mode

The above code contains a class called Counter and it inherits ObservableObject. SwiftUI makes use of the subscriber-publisher model. In this case, the Counter class publishes the count variable.

struct ContentView: View {

    @StateObject private var counter = Counter()

    var body: some View {
        VStack {
            Text("Counter: \(counter.count)")

            Button("increment in parent") {
                counter.count += 1
            }

            ChildView(counter: counter)
        }
        .padding()
    }
}
Enter fullscreen mode Exit fullscreen mode

In the main view, we declare a Counter object and initialise it.

@StateObject private var counter = Counter()

With that code, we now have access to the Counter class. This allows us to change the value of count in the Counter class. Once changed, the class then publishes said changes to any views listening.

We can even pass the Counter instance down to a child class.

The chain of events are as follows:

  • The data is changed in the main view
  • The class would publish the changes
  • The changes would be reflected in the child view

In other words, the child view only observes and display the changes made to the value.

This would be an example of the child view.

struct ChildView: View {

    @ObservedObject var counter: Counter

    var body: some View {
        VStack {
            Text("Counter: \(counter.count)")

            Button("increment in child") {
                counter.count += 1
            }
        }
        .padding()
    }
}
Enter fullscreen mode Exit fullscreen mode

The counter variable is now a parameter for the child view. We do not initialise it here, unlike in the main view. We are only observing from the child view and not actually changing the value. Whatever happens in the main view is then being propagated down to the child view.

This has been the old way. The current code is a handfull. So let's simplify the code.

Let's strip the code down to what we know, without ObservableObject and the @Published keyword.

class Data {
    var firstName = "Mirza"
    var lastName = "Muhd"
}
Enter fullscreen mode Exit fullscreen mode

The logical next step is to do this.

@State private var data = Data()

Notice that it's not @StateObject but @State.

The nuance with this is that SwiftUI watches the data instance itself and not its contents. It ties back into the whole struct vs classes discussion. This is where @Observable comes into play.

You can continue using the above code. You just have to include a few lines.

import Observation
import SwiftUI

@Observable
class Data {
    var firstName = "Mirza"
    var lastName = "Muhd"
}
Enter fullscreen mode Exit fullscreen mode

Now, you can use @State private var data = Data() and the code works as expected.

Behind the scenes, SwiftUI is doing tons of heavy lifting. It's lesser code and looks cleaner.

Top comments (0)