100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Swift

@State and @Binding

Understand how @State owns local view state and how @Binding lets child views read and write that state without owning it themselves.

State & Data FlowBeginner9 min readJul 8, 2026
Analogies

@State and @Binding

SwiftUI's declarative model requires a clear answer to the question 'who owns this piece of data?' @State answers that for simple, view-local values: it marks a property as owned and persisted by a specific view instance, surviving across body re-evaluations even though the struct itself is recreated each render. @Binding, in contrast, marks a property as *not* owned by the current view—it's a reference to state that lives somewhere else (a parent view's @State, or a model object), passed down so a child can read and mutate it without becoming the source of truth itself. Together they let you compose reusable child views that manipulate a parent's data safely.

🏏

Cricket analogy: The team's official scorer (a @State-holding view) owns and persists the running total across every over even as commentary changes; a substitute fielder brought on briefly (a @Binding-holding child view) doesn't own the score, just reads and can appeal to affect it via the real scorer.

How @State Works Under the Hood

A SwiftUI View is a value type (usually a struct), and its body is recomputed frequently—every re-render creates a new struct instance. If a plain var held your data, it would reset every time. @State solves this by storing the actual value outside the struct, in storage managed by SwiftUI's rendering engine, keyed to that specific instance's identity in the view tree. The property wrapper gives you a projected value ($property) that produces a Binding, and reading property returns the current stored value. @State should almost always be private, since it represents data owned exclusively by that view.

🏏

Cricket analogy: A scorecard printed fresh for every over would forget the running total if it just used a blank sheet each time; @State instead keeps the actual tally in a separate ledger tied to that specific match, so a new scorecard printout still shows the correct persisted score, and that ledger should be for the scorer's eyes only ('private'), like a chess player's clock — not shared beyond the innings.

swift
struct FavoriteToggle: View {
    @State private var isFavorited = false

    var body: some View {
        Button {
            isFavorited.toggle()
        } label: {
            Image(systemName: isFavorited ? "star.fill" : "star")
                .foregroundStyle(isFavorited ? .yellow : .gray)
        }
    }
}

Passing State Down with @Binding

When a child view needs to modify a parent's @State, you don't pass the raw value—you pass a Binding using the $ prefix. The child declares the parameter with @Binding var someValue: T, and any mutation the child makes (someValue = newValue) writes straight through to the parent's storage. This is the mechanism behind reusable controls: a custom RatingControl component, for example, can be dropped into any screen and wired to whatever @State the caller owns, without RatingControl needing to know or care where that state ultimately lives.

🏏

Cricket analogy: A reusable "run-rate widget" component doesn't need to know which match it belongs to — the app passes it a Binding to that match's actual score via $score, so when the widget's user taps to correct a run, it writes straight through to the real scorecard, no matter which ground is hosting the game.

swift
struct RatingControl: View {
    @Binding var rating: Int
    let maximum = 5

    var body: some View {
        HStack {
            ForEach(1...maximum, id: \.self) { star in
                Image(systemName: star <= rating ? "star.fill" : "star")
                    .onTapGesture { rating = star }
            }
        }
    }
}

struct ReviewScreen: View {
    @State private var rating = 0

    var body: some View {
        RatingControl(rating: $rating)
    }
}

A useful mental model: @State is the well, @Binding is the bucket-and-rope. The well (parent) actually holds the water (data); the rope (Binding) lets someone standing elsewhere (the child view) draw from and pour into that same well, but they never own a separate well of their own.

Never mark @State as non-private and pass the raw property to a child expecting mutation—Swift won't even compile that for a @Binding parameter, but it's tempting to instead duplicate the value into a new @State in the child, which silently breaks the two-way link and causes UI desync bugs.

Constant Bindings for Previews

For SwiftUI previews or read-only contexts, Binding.constant(value) creates a Binding that ignores writes, which is handy for previewing a child view that expects a @Binding without wiring up a full parent.

🏏

Cricket analogy: Setting up a practice net session with a "dead ball" that a coach uses purely to demonstrate batting stance without it actually counting toward any score is like Binding.constant — useful for previewing technique without wiring up a real match.

  • @State stores view-local data outside the struct, so it survives body re-evaluations for that view's identity.
  • @State properties should be declared private since they represent data the view exclusively owns.
  • @Binding represents a reference to state owned elsewhere; it never creates its own storage.
  • Use the $ prefix on a @State property to produce a Binding to pass to a child view.
  • Mutating a @Binding writes through to the original @State, keeping parent and child in sync.
  • Binding.constant(_:) is useful for previews of views that require a Binding parameter.

Practice what you learned

Was this page helpful?

Topics covered

#Swift#IOSWithSwiftUIStudyNotes#MobileDevelopment#StateAndBinding#State#Binding#Works#Under#StudyNotes#SkillVeris