SwiftUI is all about data-driven UI. Property wrappers help SwiftUI decide when and how a view should update when the underlying data changes. This post covers the five key wrappers you’ll use most often — with short examples.
1. @State
— Local State for a View
- Use case: Simple values owned by a view (Bool, Int, String).
- Behavior: When the value changes, SwiftUI re-renders the view.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increase") { count += 1 }
}
}
}
Here we have a CounterView
with a Text("\(count)")
and a Button
, when count
changes, SwiftUI calls body
again, notices only the Text
has different content, and updates just that label — the Button
isn’t redrawn unnecessarily.
2. @Binding
— Share and Mutate Parent State
- Use case: Let a child view read and write a parent’s
@State
without copying it. - Behavior:
@Binding
provides a two-way connection.
struct ParentView: View {
@State private var isOn = false
var body: some View {
VStack {
Text(isOn ? "On" : "Off")
ToggleView(isOn: $isOn)
}
}
}
struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Enable", isOn: $isOn)
}
}
🔑 Use $
to pass a binding ($isOn
) and receive it with @Binding
in the child.
3. ObservableObject
— The Observable Class Protocol
- Use case: For reference-type models that broadcast changes to views.
- Behavior: Conform a class to
ObservableObject
so views can observe it.
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
}
ObservableObject
works together with @Published
, @StateObject
, @ObservedObject
, and @EnvironmentObject
.
4. @Published
— The Change Notifier (used inside ObservableObject
)
- Use case: Mark properties inside an
ObservableObject
that should trigger view updates. - Behavior: When a
@Published
property changes, the object emits updates.
class UserSettings: ObservableObject {
@Published var username = "Guest"
@Published var isLoggedIn = false
}
Note: @Published
notifies when the property is set. Mutating internal state of reference types may not trigger updates unless the @Published
property itself changes or you manually send objectWillChange
.
5. @StateObject
— Create & Own an ObservableObject
- Use case: View creates and owns an
ObservableObject
instance (lifecycle tied to view). - Behavior: SwiftUI instantiates it once for the view’s lifetime.
class TimerModel: ObservableObject {
@Published var seconds = 0
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
}
}
struct TimerView: View {
@StateObject private var timer = TimerModel()
var body: some View {
Text("Time: \(timer.seconds)")
}
}
6. @ObservedObject
— Observe an External ObservableObject
- Use case: The view does not own the object; it only observes changes.
- Behavior: Use when an ObservableObject instance is created elsewhere and injected into the view.
struct ProfileView: View {
@ObservedObject var settings: UserSettings
var body: some View {
Text("Hello, \(settings.username)")
}
}
7. @EnvironmentObject
— App- or Hierarchy-wide Shared Data
- Use case: Share a model across many views without passing it explicitly.
- Behavior: Provide it high in the view tree with
.environmentObject()
and access via@EnvironmentObject
.
@main
struct MyApp: App {
@StateObject private var settings = UserSettings()
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(settings)
}
}
}
struct ChildView: View {
@EnvironmentObject var settings: UserSettings
var body: some View {
Toggle("Dark Mode", isOn: $settings.isLoggedIn) // example
}
}
Quick Recap — When to Use Which
@State
→ local view-only state.@Binding
→ child modifies parent state (two-way).ObservableObject
→ make a class observable.@Published
→ mark properties insideObservableObject
to notify changes.@StateObject
→ view creates & owns the observable object.@ObservedObject
→ view observes an externally created object.@EnvironmentObject
→ share a single object across many views.
Final Tips & Common Mistakes
- Don’t use
@ObservedObject
to create objects — use@StateObject
when the view is responsible for creation. - Prefer
@Binding
over passing closures for simple two-way updates. @EnvironmentObject
is handy, but avoid overusing it for everything — explicit dependencies are easier to reason about.- Remember
@Published
notifies on set — if you mutate a property deeply (e.g., an array element), consider reassigning the published property or use methods that trigger changes.