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:

  • 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

  • @State properties must be stored properties, and they must use var.
  • SwiftUI owns the storage and updates the view when the value changes.
  • $name and $isEnabled create Binding values for controls that need two-way updates.
  • Keep state private when the view is the owner.

Examples you can copy

Example 1: Minimal state declaration

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 var inside the view.
  • If a control complains about needing a Binding, pass $property instead of property.
  • 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 @Binding or 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 $value to pass a Binding to controls like TextField and Toggle.
  • For shared state, use @Binding, @StateObject, or @ObservedObject instead.