swiftui · tutorial · components · state
Extracting a reusable SwiftUI component cleanly
What we’re doing
We start with a single, concrete SwiftUI view and extract part of it into a reusable component.
The goal is not just reuse, but:
- clean ownership
- predictable data flow
- readable code
This tutorial focuses on how to extract, not just that you can.
Why this works
This approach relies on SwiftUI views being cheap, disposable values:
The starting point
Imagine a simple settings row:
struct SettingsRow: View {
@State private var isEnabled = false
var body: some View {
HStack {
Text("Enable notifications")
Spacer()
Toggle("", isOn: $isEnabled)
}
.padding()
}
}
This works — but the toggle is tightly coupled to the row.
The problem with naive extraction
A common first attempt looks like this:
struct ToggleView: View {
@State var isOn: Bool
var body: some View {
Toggle("", isOn: $isOn)
}
}
This compiles, but it breaks the mental model.
Why?
- The child now owns state it should not own
- Changes do not propagate back to the parent
- Reuse becomes unpredictable
The rule of thumb
State belongs to the view that decides.
Reusable components receive bindings.
If a view:
- decides → it owns
@State - reflects → it receives
@Binding
Step 1: Identify ownership
In our example:
- The screen decides whether notifications are enabled
- The row and toggle merely reflect that decision
So state must move up.
Step 2: Lift state to the parent
struct SettingsView: View {
@State private var notificationsEnabled = false
var body: some View {
SettingsRow(
title: "Enable notifications",
isOn: $notificationsEnabled
)
}
}
Now the parent owns the truth.
Step 3: Convert the row to a reusable component
struct SettingsRow: View {
let title: String
@Binding var isOn: Bool
var body: some View {
HStack {
Text(title)
Spacer()
Toggle("", isOn: $isOn)
}
.padding()
}
}
Key changes:
@State→@Binding- configuration via parameters
- no hidden ownership
Why this works
- There is one source of truth
- The parent controls behavior
- The component is reusable in any context
- SwiftUI can reason about updates correctly
A quick mental checklist
Before extracting a component, ask:
- Does this view decide something?
- Yes →
@State
- Yes →
- Does it only display or forward something?
- Yes →
@Binding
- Yes →
If the answer is unclear, extract later.
Common mistakes
- Using
@Statein reusable views - Passing values instead of bindings
- Adding logic to components that should stay dumb
Key takeaways
- Reusability is about boundaries, not size
- Bindings preserve ownership
- Clean extraction makes SwiftUI code scale
- If data flow feels weird, state is probably in the wrong place
Clean components come from clean thinking.
Related