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
@Statewithout copying it. - Behavior:
@Bindingprovides 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
ObservableObjectso 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
ObservableObjectthat should trigger view updates. - Behavior: When a
@Publishedproperty 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
ObservableObjectinstance (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 insideObservableObjectto 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
@ObservedObjectto create objects — use@StateObjectwhen the view is responsible for creation. - Prefer
@Bindingover passing closures for simple two-way updates. @EnvironmentObjectis handy, but avoid overusing it for everything — explicit dependencies are easier to reason about.- Remember
@Publishednotifies on set — if you mutate a property deeply (e.g., an array element), consider reassigning the published property or use methods that trigger changes.
