How to Use @State in SwiftUI
What you’ll build or solve
Use @State to store simple, view-local state in SwiftUI. You’ll declare a state property and know how to read it, write it, and pass a binding with $.
When this approach works best
@State works best when:
Learn Swift on Mimo
- A view owns a small piece of state, like a counter, toggle, or text input.
- The value should reset when the view goes away, since the state is tied to that view’s lifecycle.
- You want a simple way to trigger UI updates when a value changes.
This is a bad idea when multiple views need to read and write the same state. In that case, lift state up and pass it down with @Binding, or use @StateObject / @ObservedObject for shared models.
Prerequisites
- Xcode with a SwiftUI project
- Basic Swift knowledge (properties and structs)
Step-by-step instructions
Step 1: Declare state with the @State property wrapper
Declare a stored property inside a SwiftUI view and add @State. Use var, not let.
importSwiftUI
structCounterView:View {
@Stateprivatevarcount:Int=0
@Stateprivatevarname:String=""
@StateprivatevarisEnabled:Bool=false
varbody:someView {
VStack(spacing:12) {
// Reading state
Text("Count: \(count)")
Text("Name: \(name)")
// Writing state
Button("Add") {
count+=1
}
// Binding to controls using $ (projection)
TextField("Name",text: $name)
.textFieldStyle(.roundedBorder)
Toggle("Enabled",isOn: $isEnabled)
}
.padding()
}
}
#Preview {
CounterView()
}
What to look for
@Stateproperties must be stored properties, and they must usevar.- SwiftUI owns the storage and updates the view when the value changes.
$nameand$isEnabledcreateBindingvalues for controls that need two-way updates.- Keep state
privatewhen the view is the owner.
Examples you can copy
Example 1: Minimal state declaration
CSS
importSwiftUI
structFlagView:View {
@StateprivatevarisOn=false
varbody:someView {
Text(isOn?"On" :"Off")
}
}
#Preview {
FlagView()
}
Example 2: Text input state + binding
importSwiftUI
structSearchView:View {
@Stateprivatevarquery=""
varbody:someView {
TextField("Search",text: $query)
.textFieldStyle(.roundedBorder)
.padding()
}
}
#Preview {
SearchView()
}
Example 3: Counter state + mutation
importSwiftUI
structSimpleCounterView:View {
@Stateprivatevarcount=0
varbody:someView {
Button("Count: \(count)") {
count+=1
}
.padding()
}
}
#Preview {
SimpleCounterView()
}
Common mistakes and how to fix them
Mistake 1: Using let with @State
What you might do:
@Stateprivateletcount=0
Why it breaks: @State needs a mutable stored property.
Correct approach:
@Stateprivatevarcount=0
Mistake 2: Trying to change @State from outside the view
What you might do: Create a child view with @State, then expect a parent to modify that value.
Why it breaks: @State belongs to the view that declares it, so other views should not own it.
Correct approach: Lift the state to the parent and pass a binding down.
importSwiftUI
structParentView:View {
@Stateprivatevarcount=0
varbody:someView {
ChildView(count: $count)
}
}
structChildView:View {
@Bindingvarcount:Int
varbody:someView {
Button("Add") {
count+=1
}
}
}
Troubleshooting
- If you see
Cannot assign to property: 'self' is immutable, confirm the property is declared with@State varinside the view. - If a control complains about needing a
Binding, pass$propertyinstead ofproperty. - If the value resets unexpectedly, check whether the view is being recreated with a different identity (for example, inside conditionals or lists).
- If multiple views need the same state, move the state up and use
@Bindingor an observable object.
Quick recap
- Declare view-local state with
@State private var value = .... - Read the value normally inside
body. - Write the value with normal Swift assignment.
- Use
$valueto pass aBindingto controls likeTextFieldandToggle. - For shared state, use
@Binding,@StateObject, or@ObservedObjectinstead.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot