跳转至
Structure

StateObject

A SwiftUI property wrapper that instantiates and stores an observable object in state.

Declaration

@frozen @propertyWrapper struct StateObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject

Overview

  • Think of @StateObject as a combination of @State and @ObservedObject.
  • Like @ObservedObject, this type subscribes to the observable object and invalidates a view whenever the observable object changes.
  • Unlike @ObservedObject, @StateObject holds on to its value even when the view is invalidated and redrawn.

Usage

In the following example, an observable object class AppModel is instantiated and stored in a @StateObject:

class AppModel: ObservableObject {
    @Published var foo: Bool = false
}

struct ExampleView: View {
    @StateObject var appModel = AppModel()

    var body: some View {
        Text("Hello World")
    }
}
A view displaying the text "Hello World"; the view instantiates an observable object class AppModel and stores it in a @StateObject.

How it works

The following is the basic structure of a @StateObject:

struct StateObject<ObjectType: ObservableObject>: DynamicProperty {
    var wrappedValue: ObjectType { get }

    init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
}
It's important to note that the initializer takes an @autoclosure expression. This means that the following code is evaluated lazily:

@StateObject var appModel = AppModel()
AppModel is only initialized once per the lifetime of the View, Scene or App that contains the @StateObject. This is made possible by the @autoclosure annotation, that wraps the instantiation of the app model, AppModel(), into a lazy expression at compile time, { return AppModel() }. This allows the @StateObject to call it appropriately as needed, which is once per its parent's lifetime.

Creating bindings

Just like @State, @ObservedObject and @EnvironmentObject, @StateObject allows you to create a Binding from its wrapped value type using the $ syntax.

For example:

class AppModel: ObservableObject {
    @Published var flag: Bool = false
}

struct ExampleView: View {
    @StateObject var appModel = AppModel()

    var body: some View {
        Toggle("Flag", isOn: $appModel.flag)
    }
}
A gif displaying a toggle named "Flag" switching on and off; the view instantiates an observable object class AppModel with a bool variable called flag that acts as a binding for isOn in the toggle by way of a@StateObject.

In this example, AppModel contains a boolean, flag, which is represented by a Toggle in ChildView. Toggle requires a Binding to read and write whether it is on.

Comparison with @ObservedObject

Consider the following:

struct ExampleView: View {
    class ViewModel: ObservableObject {
        init() {
            print("Initialized")
        }
    }

    struct ToggleDescription: View {
        let value: Bool

        @StateObject var viewModel = ViewModel()

        var body: some View {
            Text("The value is: \(String(describing: value))")
        }
    }

    @State var foo = false

    var body: some View {
        VStack {
            ToggleDescription(value: foo)

            Toggle("Refresh", isOn: $foo)
        }
    }
}
A gif displaying view containing a VStack with a toggle description reading "The value is: _", populated with true or false dependent on the bool value, and a toggle called "Refresh" that is used to change the bool value; flipping the toggle caused "Initialized" to be printed once in the console because the view uses a state object.

ExampleView creates a vertical stack of a Toggle, and a view that describes the toggle, ToggleDescription.

ToggleDescription also contains a ViewModel, that is instantiated and held by @StateObject. The ViewModel prints on initialization. Run this code and observe that the following is printed:

Flip the toggle twice. Note that even though ToggleDescription is refreshed, nothing is printed further.

Now consider the following:

struct ExampleView: View {
    class ViewModel: ObservableObject {
        init() {
            print("Initialized")
        }
    }

    struct ToggleDescription: View {
        let value: Bool

        @ObservedObject var viewModel = ViewModel()

        var body: some View {
            Text("The value is: \(String(describing: value))")
        }
    }

    @State var foo = false

    var body: some View {
        VStack {
            ToggleDescription(value: foo)

            Toggle("Refresh", isOn: $foo)
        }
    }
}
A gif displaying view containing a VStack with a toggle description reading "The value is: _", populated with true or false dependent on the bool value, and a toggle called "Refresh" that is used to change the bool value; flipping the toggle caused "Initialized" to be printed each time in the console because the view uses an observed object.

This example is identical to the previous example except for the fact that @StateObject has been replaced with @ObservedObject. Run this code now, and observe the following print again:

Initialized

Initialized
Now flip the toggle twice. The console will print the following:

Initialized
Initialized
This highlights the fundamental difference between @StateObject and @ObservedObject.

  • @StateObject instantiates and holds the object in state
  • @ObservedObject is assigned an object, and does not hold it in

Usage with App

@StateObject provides a great way to initialize global, application-wide models.

In the following example, a @StateObject is instantiated in MyApp, and passed down to ExampleView as an environment object.

class AppModel: ObservableObject {
    @Published var foo: Bool = false
}

@main
struct MyApp: App {
    @StateObject var appModel = AppModel()

    var body: some Scene {
        WindowGroup {
            ExampleView()
                .environmentObject(appModel)
        }
    }
}

struct ExampleView: View {
    @EnvironmentObject var appModel: AppModel

    var body: some View {
        Text("Hello World")
    }
}
A view displaying the text "Hello world"; the main app instantiates a state object and passes it down to the view as an environment object.

Availability

iOS 14.0+

macOS 11.0+

tvOS 14.0+

watchOS 7.0+

Topics


Instance Property

projectedValue A projection of the state object that creates bindings to its properties.

wrappedValue The underlying value referenced by the state object.


Initializer

init(wrappedValue:) Creates a new state object with an initial wrapped value.