@State — What It Is, Purpose and Usage in SwiftUI

Author: IT Sectr Published: 2026-06-19 Reading time: 7 min

@State is a Property Wrapper in SwiftUI for managing local state within a single view. SwiftUI automatically redraws the view each time a @State property changes, making the interface reactive without manual update calls. According to Apple Developer Documentation (2025), @State is recommended for simple types and structures belonging to a single view. @State is the simplest way to add interactivity to a SwiftUI interface.

Key Takeaways

  • @State — Property Wrapper for local state belonging to a single view
  • Automatic update — SwiftUI re-invokes body when a @State property changes
  • Simple types — @State works with String, Int, Bool, enum, and structs
  • Do not pass to child views — use @Binding for changes from child components
  • private — @State properties are always declared with the private modifier

What is @State in SwiftUI?

@State is a Property Wrapper built into SwiftUI that allows a view to store and track its own state. When a @State value changes, SwiftUI automatically redraws the view by re-invoking the body property. This is the foundation of reactive programming in SwiftUI: the developer declares the state, and the framework handles interface synchronization.

@State creates a storage area in the heap managed by SwiftUI. This area is persistent — it survives repeated initializations of the view struct, which occur with every render. SwiftUI uses the view's identifier (generated from its position in the hierarchy) to bind the @State property to a specific view. Thanks to this, the state is not reset when the parent view updates.

An important limitation: @State is intended only for Value types (structs, enums, primitives). For reference types (classes), use @StateObject or @ObservedObject. If you assign a class to a @State property, SwiftUI will not be able to detect changes inside the object — only a complete reference replacement.

How does @State work under the hood?

SwiftUI implements @State through an internal Storage mechanism. Each @State property gets a dedicated memory cell stored in a special storage container of the view. When a write to wrappedValue occurs, SwiftUI notifies its dependency graph via didSet about the need for redrawing.

swift
struct ContentView: View {
    @State private var name: String = "User"
    @State private var isLoggedIn: Bool = false

    var body: some View {
        VStack {
            Text("Hello, \(name)")
            Button(isLoggedIn ? "Log Out" : "Log In") {
                isLoggedIn.toggle()
            }
        }
    }
}

In the example, there are two @State properties: name (String) and isLoggedIn (Bool). When isLoggedIn.toggle() is called, SwiftUI marks ContentView as needing an update and re-invokes body in the next render cycle. The key point: @State properties are always declared with the private modifier — this signals that the state exclusively belongs to the current view and should not be changed from outside directly.

To observe changes, SwiftUI uses CurrentValueSubject from Combine. Each @State property creates a hidden publisher that notifies the system on every change. This allows SwiftUI to redraw only the minimally necessary set of views, avoiding full hierarchy updates.

When to use @State in a project

@State is optimal for simple local states: text fields in search, boolean flags for modal windows, settings toggles, counters, selected list items. If a value is used only in one view and its child components (via @Binding), @State is the right choice. For states that should survive view dismissal (e.g., form data), @State also works as long as the view remains in the hierarchy.

  • Text fields — @State for storing entered text in TextField
  • Boolean flags — @State for showing/hiding modal windows and sheets
  • Element selection — @State for tracking the selected tab or row
  • Counters — @State for numeric values with increment/decrement
  • Intermediate computations — @State for caching results inside a view

Do not use @State for global application states, caching network data, or objects used across multiple screens. @StateObject and @EnvironmentObject are designed for these purposes. Also, @State is not suitable for storing large amounts of data — every change will redraw the entire view.

@State and @Binding: working together

@Binding is a bridge between @State in a parent view and a child view that needs to modify that state. The parent declares @State, and the child component receives a Binding via the $ projection. Changing the Binding in the child view automatically updates the @State in the parent — and vice versa. This ensures a unidirectional data flow with feedback capability.

swift
struct ParentView: View {
    @State private var text: String = ""

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

struct ChildView: View {
    @Binding var text: String

    var body: some View {
        TextField("Enter text", text: $text)
    }
}

In the listing, ParentView owns the @State text, and ChildView receives $text as a Binding. The TextField inside ChildView binds to this Binding via text: $text. When the user types in the TextField, the value changes in ChildView through the Binding, which causes an update of @State in ParentView. Both views are redrawn with the new value.

Common mistakes when working with @State

The most common mistake is assigning a class to a @State property. If you write @State var model = MyClass(), SwiftUI will not be able to track changes to properties inside the class — only object replacement. For classes, always use @StateObject. The second common problem is declaring @State without the private modifier, which violates the principle of state encapsulation.

Passing @State directly to a child view without $ is another typical mistake. If you pass TextField(text: text) instead of TextField(text: $text), the child component receives just a string, not a Binding. Text changes in the TextField will not be synchronized with the parent's @State. Always use the $ projection to pass Binding.

The third mistake is multiple @State properties for related data. If several values logically form a single whole (e.g., form fields), combine them into one struct with a single @State. This simplifies passing state to child views and reduces the number of individual update triggers.

Examples of using @State in SwiftUI

@State is used in most SwiftUI projects for basic interactivity. Consider a login form example where @State manages text fields and loading state. This pattern appears in every application — from simple notes to complex enterprise solutions.

swift
struct LoginView: View {
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var isLoading: Bool = false
    @State private var errorMessage: String?

    var body: some View {
        Form {
            TextField("Email", text: $email)
            SecureField("Password", text: $password)
            Button("Log In") {
                login()
            }.disabled(isLoading)
        }
    }

    private func login() {
        isLoading = true
        // Perform network request
    }
}

In the example, there are four @State properties: email and password for form fields, isLoading for loading indication, and errorMessage for displaying errors. Each property independently manages its part of the interface. When isLoading changes, the button is automatically disabled via disabled(isLoading) — without manual UI updates.

Frequently Asked Questions

Why is @State declared with private?

@State is designed for the local state of a specific view. The private modifier ensures that other components cannot change it directly, breaking encapsulation. For external access, use the $ projection.

Can @State hold an array or dictionary?

Yes, @State supports arrays and dictionaries since they are Value types. However, when an element of an array changes, SwiftUI redraws the entire view. For large lists, @StateObject with @Published is more efficient.

What happens when nil is assigned to a @State property with an Optional type?

@State works correctly with Optional types. When nil is assigned, SwiftUI detects the change and redraws the view. This is convenient for states like errorMessage: String?, where nil means no error.

How does @State behave when the view reappears?

@State preserves the value as long as the view remains in the hierarchy. If the view is removed from the hierarchy and added again, @State is re-initialized with the default value. For persistence, use @AppStorage.

Can @State changes be animated?

Yes, wrap the change in withAnimation: withAnimation(.easeInOut) { isExpanded.toggle() }. SwiftUI animates the transition between the old and new interface state with the specified animation type.

Summary

  • @State — Property Wrapper for local state of a single view, automatically updating the interface
  • Works with simple types: String, Int, Bool, as well as structs and enums
  • Does not work with reference types (classes) — use @StateObject
  • Always private — state should not be changed from outside directly
  • $ projection — creates a Binding for passing change rights to child views
  • Multiple @State in one view — normal practice for independent states
  • withAnimation — allows animating changes to @State properties

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also