This content originally appeared on DEV Community and was authored by Karthik Pala
One of the most magical things about SwiftUI is how little code you need to keep your UI in sync with your data. Declare a property with @State
, mutate it, and your view just updates. Simple, right?
But if you’ve ever wondered what’s really happening when you use @State
, this article takes a peek under the hood.
Why Do We Even Need @State
?
Lets take below example
struct CounterView: View {
private var count = 0
var body: some View {
Button("Tap \(count)") {
count += 1
}
}
}
The above code looks good right? Create a button that says “Tap Count” plus the number of times the button has been tapped, then add 1 to tapCount whenever the button is tapped
But this doesn’t compile because CounterView
is a struct and you can’t change properties freely. Okay lets keep this aside for a minute, What other problems do we have with above code?
SwiftUI views get created and destroyed all the time as SwiftUI updates the UI tree.
If count
were just a plain Int
property on a struct, it would reset to 0
every time the view was recreated. We’d lose our state instantly.
This is where @State
comes in. It tells SwiftUI:
“Hey, this piece of data should survive across view reloads.”
For example:
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Tap \(count)") {
count += 1
}
}
}
What @State
Really Is
@State
is a property wrapper provided by SwiftUI. When you write:
@State private var count = 0
Under the hood, the State
type looks like this (simplified assumption):
@propertyWrapper
struct State<Value>: DynamicProperty {
init(initialValue: Value)
var wrappedValue: Value { get nonmutating set }
var projectedValue: Binding<Value> { get }
}
So, count
is just wrappedValue
inside a property wrapper. But the important part is where that value is stored.
Notice the nonmutating set
on wrappedValue
— this is what allows us to change a @State variable inside a struct View even though structs are normally immutable in SwiftUI’s body.
Note: See the section on DynamicProperty below to understand how it helps the State
property wrapper
Where Does the Value Live?
When you set @State var count = 0
, the value 0
is not stored inside your view struct
Instead, SwiftUI maintains a hidden state storage that lives outside the view. Each piece of state is associated with the identity of the view in the UI hierarchy.
Think of it like this:
Every time SwiftUI re-creates CounterView
, it reattaches _count
to the same storage box. That’s why the state survives view updates.
How Updates Trigger UI Refresh
When you mutate a @State
variable:
count += 1
The setter does two things:
- Updates the underlying stored value in SwiftUI’s state system.
- Marks the view as dirty and schedules a re-render.
That’s why the UI automatically refreshes with the new value — SwiftUI invalidates the current body and recomputes it.
@State
and Binding
When you use the $
prefix, you don’t get the raw value — you get a Binding
:
$count // Binding<Int>
A Binding
is essentially a lightweight reference to the same state storage. This allows child views to read and write the parent’s state without owning it.
Example:
TextField("Name", text: $name)
Here, TextField
can directly mutate the name
state in the parent view.
Lifecycle of a @State
Property
At runtime, the flow looks like this:
- SwiftUI builds the view struct (
CounterView
). - It sees a
@State
property and checks if there’s existing storage. - If storage exists, it reuses it; otherwise, it creates a new storage box.
- The view’s body runs, reading
count
viawrappedValue
. - If you change
count
, SwiftUI updates storage and schedules the body to recompute.
A Toy Implementation of @State
SwiftUI is a closed-source Apple framework, but because property wrappers in Swift are a public language feature, we can only imagine and re-implement how @State could work.
Of course, the real @State
is tightly integrated with SwiftUI’s runtime. But we can mimic the idea with an example property wrapper:
@propertyWrapper
public struct State<Value>: DynamicProperty {
private var storage: StateStorage<Value>
public init(wrappedValue: Value) {
self.storage = .init(initialValue: wrappedValue)
}
public var wrappedValue: Value {
get { storage.value }
nonmutating set { storage.value = newValue }
}
public var projectedValue: Binding<Value> {
Binding(
get: { self.wrappedValue },
set: { self.wrappedValue = $0 }
)
}
}
This obviously doesn’t persist across view recreations, but it shows the principle:
-
wrappedValue
gives you the value. - Mutating it triggers a refresh.
-
$property
gives you aBinding
.
The above example is inspired from the State property wrapper implementation in OpenSwiftUI project here
What’s DynamicProperty Doing Here?
In SwiftUI, some property wrappers (like @State
, @ObservedObject
, @EnvironmentObject
, @AppStorage
, etc.) conform to the DynamicProperty
protocol.
public protocol DynamicProperty {
mutating func update()
}
This protocol lets SwiftUI know:
-
This property participates in the SwiftUI data flow.
- SwiftUI calls
update()
before recomputing the view’s body. - That’s how the property wrapper gets a chance to reconnect to the right storage or refresh its bindings.
- SwiftUI calls
-
Why it matters for @State:
- Every time SwiftUI re-renders a view, a new struct instance of your view is created.
- Without
DynamicProperty
, the State wrapper would just get re-initialized, losing its data. - With
DynamicProperty
, SwiftUI ensures the wrapper reattaches to its persistent state storage instead of resetting.
Understanding how @State
works demystifies a lot of SwiftUI. It’s not magic — it’s just clever indirection and lifecycle management. By keeping state outside the view struct, SwiftUI ensures your UI is always a pure function of its data, while your data itself remains persistent.
So the next time you write @State var count = 0
, remember: you’re really just getting a little persistent box managed by SwiftUI, with automatic UI updates wired in.
References
1) Opensource implementation of SwiftUI – https://github.com/OpenSwiftUIProject/OpenSwiftUI
2) https://forums.swift.org/t/how-does-swiftui-find-state-properties-in-views/79984/3
3) https://fatbobman.com/en/posts/swiftui-state/
This content originally appeared on DEV Community and was authored by Karthik Pala