Skip to Content
Course content

92: ObservableObject and @Published

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've mostly relied on @State to keep our views in sync. But as your app grows, you'll realize that putting all your logic inside a View struct is a recipe for a mess. You want your data and your business logic to live in their own dedicated classes. That's where ObservableObject comes in.

Why can't I just use @State inside a class?

It's a common point of confusion. @State is specifically designed for value types (like structs) that are owned by the view. Classes are reference types. When you change a property inside a class, SwiftUI has no way of knowing that the internal data changed because the reference to the class instance itself remains exactly the same.

To fix this, we use the ObservableObject protocol. By conforming your class to this protocol, you're telling SwiftUI: "This object is capable of announcing when its data changes." But just conforming to the protocol isn't enough; you have to tell SwiftUI which specific properties should trigger that announcement.

What is @Published actually doing?

Think of @Published as a megaphone. When you mark a property with @Published, SwiftUI automatically creates a publisher for that variable. Every time that value is set, the object sends out a signal saying, "Hey, I've changed! Anyone watching me needs to redraw themselves."

Let's look at a real example. Imagine we're building a shopping basket. We don't want the UI logic mixed in with the price calculations.

class ShoppingBasket: ObservableObject {
    @Published var items: [String] = []
    @Published var totalWeight: Double = 0.0
    
    // This is a regular property. Changing this won't trigger a UI refresh.
    var internalLogId: String = "LOG_123"

    func addItem(_ item: String, weight: Double) {
        items.append(item)
        totalWeight += weight
    }
}

In this snippet, if you add an item, the items array and the totalWeight change. Because they are marked with @Published, any view observing this ShoppingBasket instance will automatically reload. However, if you changed internalLogId, the UI wouldn't budge. This is actually a good thing—you don't want your app re-rendering for data the user can't even see.

How do I actually connect this class to my View?

Creating the ObservableObject is only half the battle. You need to tell the View to listen. You'll typically use @StateObject when you are creating the object for the first time, and @ObservedObject when you're passing an existing object into a subview.

Here is how that looks in practice:

struct BasketView: View {
    // We use @StateObject because this view "owns" the basket
    @StateObject var basket = ShoppingBasket()

    var body: some View {
        VStack {
            Text("Items in basket: \(basket.items.count)")
            Text("Total Weight: \(basket.totalWeight, specifier: "%.2f") kg")
            
            Button("Add Apple") {
                basket.addItem("Apple", weight: 0.2)
            }
        }
    }
}

I've seen a lot of developers use @ObservedObject everywhere, but be careful. If the view is destroyed and recreated (which happens often in SwiftUI), an @ObservedObject can be reset. @StateObject ensures the object persists as long as the view exists.

Do I have to mark every single property as @Published?

Definitely not. In fact, you shouldn't. Every time a @Published property changes, SwiftUI evaluates the body of every view watching that object. If you have a massive object with 50 properties and 40 of them are @Published, you might end up with performance stutters because the UI is refreshing way more often than it needs to.

Only mark the properties that directly impact what the user sees on the screen. If it's a helper variable, a cache, or a private ID, keep it as a standard var.




📋 Practical Task

Exercise: Implementing a UserProfile Manager

You need to build a simple user profile system where the UI updates automatically when the user's information changes.

  • Create a class named UserProfile that conforms to ObservableObject.
  • Add two @Published properties: username (a String) and followerCount (an Int).
  • Add a regular property called lastLoginDate (a Date) that is not published.
  • Create a ProfileView that uses @StateObject to instantiate the UserProfile.
  • In the view, display the username and followerCount in Text views.
  • Add a button that increments the followerCount by 1 each time it's tapped.

Goal: Verify that the follower count updates on the screen immediately when the button is pressed, proving that the ObservableObject is successfully communicating with the view.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.