State
Where a SwiftUI view keeps memory that survives its own recreation.
A SwiftUI view is a struct. It's created, its body is read, and it's thrown away — constantly, many times a second during an animation. So a plain var in a view is useless as memory: the next recreation resets it.
@State solves that by keeping the value *outside* the struct. SwiftUI allocates storage attached to the view's position in the hierarchy, and the property wrapper reads and writes that storage. The struct comes and goes; the state stays.
Writing to it also tells SwiftUI the view is invalid, so body is re-read and the UI updates. That's the whole loop: state changes, body re-runs, the framework diffs the result.
@State is owned by one view. $ projects a Binding for children to write through.struct Counter: View { @State private var count = 0 // storage lives outside the struct var plain = 0 // reset on every recreation — not memory
var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } // triggers a body re-read } }}
// @State should be private and owned by this view.// To hand write access to a child, pass a Binding:struct Parent: View { @State private var isOn = false var body: some View { Toggle("On", isOn: $isOn) } // $ makes a Binding}
Show all 18 linesShow less
struct Counter: View { @State private var count = 0 // storage lives outside the struct var plain = 0 // reset on every recreation — not memory
var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } // triggers a body re-read } }}
// @State should be private and owned by this view.// To hand write access to a child, pass a Binding:struct Parent: View { @State private var isOn = false var body: some View { Toggle("On", isOn: $isOn) } // $ makes a Binding}A view struct is created and discarded repeatedly, while its State storage persists outside, attached to the view's identity in the hierarchy.
The struct is created and its body read.
- The struct is created and its body read.
- It's discarded and recreated — but the storage is untouched.
- SwiftUI finds the right storage by view identity. Change the identity and you get fresh state.
Under the hood
@State is a property wrapper holding a *reference* to storage SwiftUI manages, keyed by the view's identity — its type and position in the hierarchy, or its id if you gave it one. That indirection is why a let view struct can still mutate count: the struct isn't changing, the box it points at is.
Identity is the part that produces surprises. Change a view's identity — with a different .id(), by moving it between branches of an if, or by changing an element's identity in a ForEach — and SwiftUI treats it as a new view and gives it *fresh* state. That's the mechanism behind both "my state mysteriously reset" and the deliberate trick of .id(someValue) to force a reset.
The initial value is only used the first time. On later recreations the existing storage wins, so passing a constructor argument into a @State initial value doesn't update it when the argument changes — a genuinely common bug.
@State is also the reason body must stay cheap: any write re-reads it.
Quick check
Why can a @State property be mutated inside a body closure when the view is a struct?
- SwiftUI views are secretly classes
- @State stores the value in framework-managed storage and holds a reference to it, so the struct itself isn't mutated
- Because body is a mutating function
- Because the compiler makes an exception for property wrappers
Reveal answer
@State stores the value in framework-managed storage and holds a reference to it, so the struct itself isn't mutated
The wrapper holds a reference to a box SwiftUI owns. Writing goes through that reference into the box, so the struct is unchanged and no mutating is needed. It's also why the value outlives the struct: the box is keyed by view identity, not by the struct instance.
Interview question
Why does SwiftUI use structs for views when they're recreated so often?
Think first, then reveal
Because recreation is cheap and the alternative isn't. A struct is a value with no heap allocation and no reference counting — creating one is close to free, so SwiftUI can afford to build a fresh description of the UI on every change and diff it against the previous one. That's what makes the declarative model viable: body is a pure function from state to description, and the framework works out the minimal set of actual changes.
With classes you'd pay heap allocation and ARC traffic for every view on every update, and you'd have identity where you don't want it — two views describing the same thing would be different objects, so diffing would be harder rather than easier.
The consequence is that views can't hold their own memory, since the struct is thrown away. That's what @State is for: storage the framework keeps outside the struct, keyed by the view's identity in the hierarchy, with the wrapper holding a reference into it.
Identity then becomes the thing you have to be deliberate about. SwiftUI decides which storage belongs to which view by structural position or explicit .id(), so moving a view between branches of an if, or changing its id, gives it fresh state. Most of the "my state reset itself" bugs are identity changes nobody intended.
What an interviewer listens for
- Explains cheapness of value types as the enabler for diffing
- Frames body as a pure function from state to description
- Connects struct disposability to why @State exists
- Raises view identity as the thing that decides storage
Key takeaway
View structs are disposable; @State is storage SwiftUI keeps alongside them, keyed by view identity. Own state where it belongs, keep it private, and remember that changing identity resets it.