This content originally appeared on DEV Community and was authored by Harsh Prajapat
SwiftUI’s property wrappers are like magical tools that help you manage state, data flow, and environment context in a declarative way. Let’s break down the most important ones so you can see how they fit together. 

Core Property Wrappers in SwiftUI
| Wrapper | Purpose | Ownership | Typical Use |
|---|---|---|---|
@State |
Local value-type state | Owns data |
Simple UI state (e.g. toggles, counters) |
@Binding |
Two-way connection to another value | Refers to external data |
Pass state between parent and child views |
@StateObject |
Owns a reference-type observable object | Owns data |
Create and manage ObservableObject instances |
@ObservedObject |
Observes external ObservableObject
|
Refers to external data |
Watch changes in shared objects |
@EnvironmentObject |
Access shared object from environment | Refers to external data |
Share data across many views |
@Environment |
Read system/environment values | Refers to external data |
Access traits like color scheme, locale |
@AppStorage |
Read/write to UserDefaults
|
Owns data |
Persist user settings (e.g. theme, login) |
@Published |
Notify views of changes inside ObservableObject
|
Owns data |
Mark properties that trigger view updates |
@Observable |
New macro replacing ObservableObject + @Published
|
Owns data |
Cleaner, more efficient state tracking |
@Bindable |
Enables bindings to @Observable properties |
Refers to external data |
Use $property syntax with @Observable
|
Example: Using @State and @Binding
struct ParentView: View {
@State private var isOn = false
var body: some View {
ToggleView(isOn: $isOn)
}
}
struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("Enable Feature", isOn: $isOn)
}
}
Example: Using @StateObject and @ObservedObject
class CounterModel: ObservableObject {
@Published var count = 0
}
struct CounterView: View {
@StateObject private var model = CounterModel()
var body: some View {
VStack {
Text("Count: \(model.count)")
Button("Increment") {
model.count += 1
}
}
}
}
If you pass model to another view, use @ObservedObject there.
Example: Using @EnvironmentObject
class Settings: ObservableObject {
@Published var isDarkMode = false
}
struct RootView: View {
@StateObject private var settings = Settings()
var body: some View {
ContentView()
.environmentObject(settings)
}
}
struct ContentView: View {
@EnvironmentObject var settings: Settings
var body: some View {
Toggle("Dark Mode", isOn: $settings.isDarkMode)
}
}
Want to dive deeper into @Observable, @Bindable, or how these wrappers interact with SwiftUI’s rendering engine? I can walk you through advanced patterns or help refactor your code. 

This content originally appeared on DEV Community and was authored by Harsh Prajapat
Owns data
Refers to external data