← All articles

How to (not) initialize @State inside the View's init

2023 update: Since I've wrote this article Apple published some guidance regarding this topic. However, I highly recommend avoiding tampering with the identity of a view as they suggest to do for initializing @StateObject. Doing so may lead to undesirable consequences such as malfunctioning animations, potential loss of focus, performance issues, and various other issues that you definitely wouldn't want to troubleshoot.

The problem

Before we start

There's a subtle difference between the following inits. Both are initializing @State, however, since the first one doesn't rely on an external value for the greeting, it's not a problem per se. Just the same as using @State private var greeting: String = "Hello John" (and has the same limitation when it comes to external state injection too).

// Maybe an anti-pattern, but this is not a problem per se
struct SimpleView: View {
    @State private var greeting: String
    init() {
        _greeting = State(initialValue: "Hello John")
    }
    //...
}

// This **is** a problem tho
struct SimpleView: View {
    @State private var greeting: String
    init(name: String) {
        _greeting = State(initialValue: "Hello \(name)!")
    }
    //...
}

There seems to be some confusion around the @State property wrapper. So much so the top answers in all these StackOverflow questions are wrong.

TL;DR You never initialize @State inside the view's init. Use @Binding, @ObservedObject, a combination between @Binding and @State or even a custom DynamicProperty (e.g. for edit and commit/save later, here's a good example).

☝🏽 That's the straightforward(ish) solution. But there's more to learn here! This can give us a solid insight into how @State and other DynamicProperty wrappers work.

Let's start with an example.

struct SimpleView: View {
    @State private var greeting: String
    init(name: String) {
        _greeting = State(initialValue: "Hello \(name)!")
    }

    var body: some View {
        VStack {
            Text(greeting)
            Button(action: { greeting = "Hello Tim!" }) {
                Text("Update greeting")
            }
        }
    }
}

struct ContentView: View {
    @State private var name: String = "Mark"
    var body: some View {
        VStack {
            SimpleView(name: name)
            Button(action: { name = "Summer" }) {
                Text("Update name")
            }
        }
    }
}

If you run this code you'll notice that there's no way to make Hello Summer show up. That's weird since the name member in ContentView clearly holds Summer once we press the update name button.

Now, let's add some custom content with Summer's name:

if name == "Summer" {
    HStack {
        SimpleView(name: name)
        Text("☀️")
    }
}
else {
    SimpleView(name: name)
}

Everything works now. At least the first time we press the update name button. But then, it fails just like before. Looks like undefined behavior, but it's not, there is a reason behind this, and it all boils down to a State initialization misuse. The conditional view here was just an excuse, anything that would have create a SimpleView with a different structural identity would have worked.

Now, you might be tempted to use State(initialValue:) as long as you only pass constant values to the init method (e.g. SimpleView(name: "Enter name")). This would work, but needless to say, it's bad practice since it creates an unenforceable contract between the provider and the consumer of the API (in other words, it's really easy to mess up and assume a change in name should be reflected in SimpleView)

Inconsistent behavior is certainly bad for business, but if none of the above convinced you, I'll leave here a comment by one of Apple's compiler engineers:

Any consistent behavior you see is an accident. @State's initial value must be independent of any instance of the view. Chances are good this will be enforced at compile time when the necessary property wrapper functionality exists to do so.

And another one, here:

Although that will compile, @State variables in SwiftUI should not be initialized from data you pass down through the initializer; since the model is maintained outside of the view, there is no guarantee that the value will really be used. The correct thing to do is to set your initial state values inline: @State private var viewModel = SignInViewModel(...)

Join us
Sign up and be the first to know when we release new content!

What causes this behavior

The answer can be found in DynamicProperty's documentation. @State like many other stateful wrappers (e.g. @EnvironmentObject, @AppStorage, @StateObject and so on) conforms to this obscure protocol. You won't find much about it, but it's at the core of SwiftUI's magic. The overview in the docs mention:

The view gives values to these properties prior to recomputing the view’s body.

Views are always immutable, so any stateful member like greeting has to live somewhere else. When using a DynamicProperty we're telling SwiftUI the member/property needs external storage. And once that external storage is allocated for our property, SwiftUI will always read from it and assign its value to our actual view member before recomputing the body.

But wait, how can it assign a value to an immutable view?

That's the magic part. It's probably done by manipulating the actual memory representation of the View instance. The fun part is that if you create a custom DynamicProperty you can have a let property: MyDynamicProperty member and SwiftUI will still be able to inject the external value before recomputing the body. I'm not sure if this is a bug or intended, but it completely bypasses the type system.

In our case, using _greeting = State(initialValue: "Hello \(name)!") works the first time because it allocates the external storage for the view (identified by its structural identity), but fails each subsequent time since SwiftUI ignores any re-allocations (for the same identity) and updates the actual view property with the value it finds in the external storage right before recomputing the body.

StackOverflow answers

  • https://stackoverflow.com/questions/56691630/swiftui-state-var-initialization-issue
  • https://stackoverflow.com/questions/62635914/initialize-stateobject-with-a-parameter-in-swiftui
  • https://stackoverflow.com/questions/58758370/how-could-i-initialize-the-state-variable-in-the-init-function-in-swiftui