- Published on
> @State vs @Binding in SwiftUI: When to Use Each
- Authors

- Name
- Mick MacCallum
- @0x7fs
SwiftUI uses property wrappers to manage data and trigger view updates. @State and @Binding are two of the most common, and understanding when to use each is fundamental to building SwiftUI apps correctly.
@State: Owning the Truth
Use @State when a view owns the data and is the source of truth for that value:
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
The CounterView creates and owns the count variable. When count changes, SwiftUI automatically re-renders the view. The @State property wrapper stores the value outside the view's struct (since structs are value types and get recreated), ensuring it persists across view updates.
Key characteristics of @State:
- The view owns the data
- Changes trigger view updates
- Should be marked
privatesince the data is internal to the view - Typically used for simple value types (Int, String, Bool, etc.)
@Binding: Borrowing the Truth
Use @Binding when a view needs to read and write a value owned by another view:
struct ToggleRow: View {
let title: String
@Binding var isOn: Bool
var body: some View {
Toggle(title, isOn: $isOn)
}
}
struct SettingsView: View {
@State private var notificationsEnabled = true
@State private var darkModeEnabled = false
var body: some View {
Form {
ToggleRow(title: "Notifications", isOn: $notificationsEnabled)
ToggleRow(title: "Dark Mode", isOn: $darkModeEnabled)
}
}
}
SettingsView owns the state with @State. It passes a binding to ToggleRow using the $ prefix. ToggleRow can read and modify the value, but it doesn't own it—changes flow back to the parent.
Key characteristics of @Binding:
- The view does not own the data
- Creates a two-way connection to the source of truth
- Changes made through the binding update the original value
- Not marked
privatesince it must be passed in from outside
The $ Prefix
The $ prefix creates a binding from a state variable:
@State private var name = ""
// $name is a Binding<String>
TextField("Name", text: $name)
Without the $, you're accessing the value itself. With $, you're accessing a binding that can both read and write.
Data Flow Pattern
The typical pattern flows downward: parent views own state and pass bindings to children.
struct ParentView: View {
@State private var text = "" // Owns the data
var body: some View {
ChildView(text: $text) // Passes a binding
}
}
struct ChildView: View {
@Binding var text: String // Borrows the data
var body: some View {
TextField("Enter text", text: $text)
}
}
When the user types in the TextField, changes propagate through the binding back to ParentView's @State, which triggers both views to update.
Constant Bindings for Previews
When building previews or testing, use .constant() to create a read-only binding:
#Preview {
ToggleRow(title: "Test", isOn: .constant(true))
}
The view will display correctly, but toggling won't change anything since the binding always returns the same value.
Common Mistakes
Declaring @Binding when you should use @State:
// Wrong: View doesn't receive this from outside
struct BadView: View {
@Binding var count: Int // Where does this come from?
var body: some View {
Text("\(count)")
}
}
// Right: View owns this data
struct GoodView: View {
@State private var count = 0
var body: some View {
Text("\(count)")
}
}
Forgetting the $ when a binding is expected:
// Wrong: Passing the value, not a binding
TextField("Name", text: name)
// Right: Passing a binding
TextField("Name", text: $name)
Binding to Specific Properties
You can create bindings to properties of a larger state object:
struct User {
var name: String
var email: String
}
struct ProfileView: View {
@State private var user = User(name: "", email: "")
var body: some View {
Form {
TextField("Name", text: $user.name)
TextField("Email", text: $user.email)
}
}
}
The $user.name creates a Binding<String> that reads and writes just the name property.
Custom Bindings
Create custom bindings for transformed or computed values:
struct SliderView: View {
@State private var value: Double = 50
var body: some View {
VStack {
Slider(value: $value, in: 0...100)
Text("Value: \(Int(value))")
}
}
// Custom binding that clamps the value
var clampedBinding: Binding<Double> {
Binding(
get: { value },
set: { newValue in
value = min(max(newValue, 10), 90)
}
)
}
}
Choosing Between Them
Ask yourself: "Who owns this data?"
If the view creates the data and no parent cares about it, use @State:
@State private var isExpanded = false // Local UI state
If a parent view needs to know about or control the value, use @Binding:
@Binding var selectedTab: Int // Parent controls which tab is selected
For app-wide state that multiple unrelated views need to access, consider @Observable (iOS 17+) or @EnvironmentObject instead. @State and @Binding work best for state that flows through the view hierarchy in a predictable parent-to-child pattern.
Quick Reference
| Scenario | Use |
|---|---|
| Local toggle/counter | @State |
| Reusable form field | @Binding |
| Sheet presented state | @State in parent |
| Child needs to modify parent data | @Binding |
| Preview for binding component | .constant() |
The general rule: put @State as close to where the data is used as possible, and only lift it up when children need to modify it. This keeps your data flow simple and your views easy to reason about.
// Continue_Learning
Working with Optional ObservableObject in SwiftUI
Learn different approaches for handling optional ObservableObject instances in SwiftUI, from conditional rendering to wrapper patterns.
Detecting When App Enters Background and Saving State in SwiftUI
Learn how to detect app lifecycle changes in SwiftUI, save state when your app enters the background, and restore it when users return, using scenePhase and UIApplication notifications.
Implementing Undo/Redo in SwiftUI with UndoManager
Learn how to implement undo/redo functionality in SwiftUI using UndoManager, including environment injection, registering actions, and creating undo buttons for professional editing experiences.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.