SwiftUI Property Wrappers

Deciding when to use @State, @Binding, @StateObject, @ObservedObject, and @EnvironmentObject

Quick reference

A flowchart that helps decide which property wrapper should be used.

This draft by Chris Eidhof from objc.io nicely summarizes the rules and decisions outlined on this page. (original tweet)


@State

The @State property wrapper is used inside of View objects and allows your view to respond to any changes made to @State. You use @State for properties that are owned by the view that it's contained in. In other words, a view initializes its @State properties itself. It does not receive its @State properties from another object.

struct StateExample: View {
  @State private var intValue = 0

  var body: some View {
    VStack {
      Text("intValue equals \(intValue)")

      Button("Increment") {
        intValue += 1
      }
    }
  }
}

Internally, SwiftUI will store your @State property's value and persist its value throughout re-renders of your view. This makes it a good fit for state that is managed by the view itself and should be persisted when SwiftUI must discard and recreate your view instance during a refresh.

Note that you should mark your @State properties private as a best-practice. No external sources should modify your @State properties.

Deciding if you should use @State

You should use @State if:


@Binding

The @Binding property wrapper is used for properties that are passed by another view. The view that receives the binding is able to read the bound property, respond to changes made by external sources (like the parent view), and it has write access to the property. Meaning that updating a @Binding updates the corresponding property on the view that provided the @Binding.

struct BindingView: View {
  @Binding var intValue: Int

  var body: some View {
    VStack {
      Button("Increment") {
        intValue += 1
      }
    }
  }
}

This is an example of a view that receives a Binding and modifies it what a user taps a button. You would use this view as follows:

struct StateView: View {
  @State private var intValue = 0

  var body: some View {
    VStack {
      Text("intValue equals \(intValue)")

      BindingView(intValue: $intValue)
    }
  }
}

struct BindingView: View {
  @Binding var intValue: Int

  var body: some View {
    Button("Increment") {
      intValue += 1
    }
  }
}

Notice that I pass a binding to the @State wrapped intValue to BindingView by prefixing it with a $: $intValue. The projected value of a @State property is a Binding<T> that you can pass to child views so they can modify @State properties through the binding rather than directly.

Internally, SwiftUI will not keep a @Binding around when your view is discarded. It doesn't need to because the @Binding is always passed by an external source. Unlike @State where SwiftUI keeps the property around so its value persists when a view is discarded and recreated for a fresh render.

Deciding if you should use @Binding

You should use @Binding if:


@StateObject

Only available in iOS 14+, iPadOS 14+, watchOS 7+, macOS 10.16+ etc.

The @StateObject property is used for similar reasons as @State, except it's applied to ObservableObjects. An ObservableObject is always a reference type (class) and informs SwiftUI whenever one of its @Published properties will change.

class DataProvider: ObservableObject {
  @Published var currentValue = "a value"
}

struct DataOwnerView: View {
  @StateObject private var provider = DataProvider()

  var body: some View {
    Text("provider value: \(provider.currentValue)")
  }
}

Notice that DataOwnerView creates an instance of DataProvider. This means that DataOwnerView owns the DataProvider. Whenever the value of DataProvider.currentValue changes, DataOwnerView will rerender.

Internally, SwiftUI will keep the initially created instance of DataProvider around whenever SwiftUI decides to discard and recreate DataOwnerView for a fresh render. This means that a @StateObject for any given view is initialized only once.

SwiftUI sets the instance associated with your @StateObject aside and reuses it when the view that owns the @StateObject is initialized again. This means that your new view instance does not get a new instance of the property marked as @StateObject since it's reused.

In other words, a property marked as @StateObject will keep its initially assigned ObservedObject instance as long as the view is needed, even when the struct gets recreated by SwiftUI.

This is the same behavior you see in @State, except it's applied to an ObservableObject rather than a value type like a struct.

Deciding if you should use @StateObject

You should use @StateObject if:


@ObservedObject

An @ObservedObject is used to wrap ObservableObject instances that are not created or owned by the view that's used in. It's applied to the same types of objects as @StateObject, and it provides similar features, except a view doesn't create its own @ObservedObject instances. Instead, they are passed down to views like this:

struct DataOwnerView: View {
  @StateObject private var provider = DataProvider()

  var body: some View {
    VStack {
      Text("provider value: \(provider.currentValue)")

      DataUserView(provider: provider)
    }
  }
}

struct DataUserView: View {
  @ObservedObject var provider: DataProvider

  var body: some View {
    // create body and use / modify `provider`
  }
}

The DataOwnerView passes a reference to its @StateObject down to DataUserView, where the DataProvider is used as an @ObservedObject.

Internally, SwiftUI will not keep an @ObservedObject around when it discards and recreates a view if this is needed for a fresh render.

Instead, SwiftUI knows that the parent view will pass down an ObservedObject (which could be either a @StateObject if the parent owns the property, or an @ObservedObject if the parent doesn't own the property) that's used as the value for the property marked as @ObservedObject.

Deciding if you should use @ObservedObject

You should use @ObservedObject if:


@EnvironmentObject

Sometimes you have objects that are needed in various places in your app, and you might not want to pass these objects down to the initializer of each view you create. In those cases, you might want to make a dependency available to all children of a view, your App or a Scene.

You can achieve this with @EnvironmentObject:

struct EnvironmentUsingView: View {
  @EnvironmentObject var dependency: DataProvider

  var body: some View {
    Text(dependency.currentValue)
  }
}

Properties that are marked as @EnvironmentObject must conform to ObservableObject. They are configured by a parent object of the object that uses the @EnvironmentObject. So for example, we can inject an environment object from the App struct to make it available for us in all views that we create:

struct MyApp: App {
  @StateObject var dataProvider = DataProvider()

  var body: some Scene {
    WindowGroup {
      EnvironmentUsingView()
        .environmentObject(dataProvider)
    }
  }
}

An @EnvironmentObject shared the same functionality that an @ObservedObject has. Your view will re-render when one of the @EnvironmentObject's properties changes. The main difference is that @EnvironmentObject properties are made available on a much larger scale than @ObservedObject. In same cases even to your whole app.

Deciding if you should use @EnvironmentObject

You should use @EnvironmentObject if:


Want to learn more?