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.

import SwiftUI

struct CounterView: View {
    @State private var count: Int = 0
    @State private var name: String = ""
    @State private var isEnabled: Bool = false

    var body: some View {
        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

import SwiftUI

struct FlagView: View {
    @State private var isOn = false

    var body: some View {
        Text(isOn ? "On" : "Off")
    }
}

#Preview {
    FlagView()
}

Example 2: Text input state + binding

import SwiftUI

struct SearchView: View {
    @State private var query = ""

    var body: some View {
        TextField("Search", text: $query)
            .textFieldStyle(.roundedBorder)
            .padding()
    }
}

#Preview {
    SearchView()
}

Example 3: Counter state + mutation

import SwiftUI

struct SimpleCounterView: View {
    @State private var count = 0

    var body: some View {
        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:

@State private let count = 0

Why it breaks: @State needs a mutable stored property.

Correct approach:

@State private var count = 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.

import SwiftUI

struct ParentView: View {
    @State private var count = 0

    var body: some View {
        ChildView(count: $count)
    }
}

struct ChildView: View {
    @Binding var count: Int

    var body: some View {
        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.