Understanding the Differences Between @State, @Binding, and @Published in SwiftUI with Practical Examples

Property wrappers are key to SwiftUI’s magic, they add capabilities to properties and automate common UI state behaviors, such as updating views, without the need for manual boilerplate code.

In this quick tutorial, we’ll walk through some of the most commonly used SwiftUI property wrappers, with simple examples of how each one is used and what sets them apart.

@State

As stated in the official documentation:
A property wrapper type that can read and write a value managed by SwiftUI.

When used, @State acts as the source of truth for a value type in the view hierarchy. Once applied to a property, the @State attribute creates a state value in the app.
Let’s take this example of a simple screen with a toggle button. Create a SwiftUI project and replace the code in ContentView.swift with the following:

@State private var isPlaying: Bool = false

var body: some View {
   Button(isPlaying ? "Pause" : "Play") {
       isPlaying.toggle()
   }
}

Why is the property annotated with @State marked as private?

SwiftUI designed @State to work locally and be tied to the view in which it is declared. Otherwise, nothing would prevent other objects from mutating its state by passing it as a dependency to another object. Marking it as private is a convention that helps encapsulate the property within its view and allows SwiftUI to manage the storage reliably.

@Binding

But what if I need to mutate an @State property from outside the view in which it is declared?

Here’s where @Binding comes in handy. Let’s take the previous example and modify it to showcase the importance of @Binding.

struct ContentView: View {
    @State private var isPlaying: Bool = false

    var body: some View {
        PlayerButton(isPlaying: $isPlaying)
    }
}

The button is currently part of the main view on the screen. Let’s say we want to extract it into its own view for better control of the code:

struct PlayerButton: View {
    @Binding var isPlaying: Bool

    var body: some View {
        VStack {
            Button(isPlaying ? "Pause" : "Play") {
                isPlaying.toggle()
            }
        }
    }
}

Now, the isPlaying property declared in the PlayerButton view is pointing to the one declared in ContentView as @State. In other words, we now have a reference to the source of truth.

@Published

Let’s spice this up by moving the isPlaying property to a view model for a cleaner approach. The view model will be a class, like the following:

import Combine

class ViewModel: ObservableObject {
    @Published var isPlaying: Bool = false
}

Now, the ViewModel owns the isPlaying property, and a Combine publisher will emit its new value to any subscriber whenever the value changes.

Let’s tweak the ContentView a little bit to leverage the new use of the Combine publisher.

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    var body: some View {
        VStack {
            Button(viewModel.isPlaying ? "Pause" : "Play") {
                viewModel.isPlaying.toggle()
            }
        }
    }
}

Marking the view model with the @StateObject property wrapper tells SwiftUI that the ContentView owns and should retain the ObservableObject (the view model, in this case).

You may notice that both approaches (@State + @Binding vs @StateObject + @Published) lead to the same result: reactive updates when the isPlaying value changes. However, it’s important to highlight a key difference between them. While you would use @State + @Binding for simple local state that lives in a struct (value type), @StateObject + @Published allows for more complex state that can be shared and reused across views, and which lives in a class (reference type).

When is it best to use one or the other?

As a simple rule of thumb, if your state logic is simple, tied to a single view, and you’re working with structs, @State@Binding is your best bet. However, if you’re dealing with complex logic and need to share the state across multiple views, you would want to use @StateObject@Published instead.

Thanks for reading – see you in the next digest!


Discover more from SweetTutos

Subscribe to get the latest posts sent to your email.

Malek
Software craftsman with extensive experience in iOS and web development.