Preview Dependency Injection Workflow
Goal
By the end of this tutorial, you will have a repeatable preview setup that injects the same feature dependencies your live screen uses. That makes previews more honest and makes missing dependencies obvious earlier.
Requirements
- Basic SwiftUI familiarity
- Xcode 15+
- A screen that reads at least one environment value or injected service
The use case
This pattern appears when a feature relies on a theme flag, analytics service, or app-specific configuration. Without a workflow, previews often drift away from the real screen because they rely on accidental defaults.
Step 1 - Start with the screen boundary
Keep the feature root responsible for wiring dependencies.
import SwiftUI
struct FeatureScreen: View {
@Environment(\.isProFeatureEnabled) private var isProFeatureEnabled
var body: some View {
VStack(spacing: 12) {
Text(isProFeatureEnabled ? "Pro mode" : "Free mode")
Text("The screen reads dependencies at the boundary.")
}
.padding()
}
}
What changed:
- the screen reads the dependency where it is used
- the boundary stays explicit
Verification:
- the screen still compiles
- the behavior changes when the environment value changes
Step 2 - Add a preview that mirrors reality
Inject the dependency in the preview instead of relying on defaults.
#Preview("Pro") {
FeatureScreen()
.environment(\.isProFeatureEnabled, true)
}
#Preview("Free") {
FeatureScreen()
.environment(\.isProFeatureEnabled, false)
}
What changed:
- the preview now documents the dependency
- you can inspect both states without changing app code
Verification:
- both previews render
- the special state is visible immediately
Step 3 - Make the setup reusable
If the same feature needs the same injected state in many previews, extract a helper.
struct FeaturePreviewWrapper<Content: View>: View {
let isProFeatureEnabled: Bool
let content: () -> Content
var body: some View {
content()
.environment(\.isProFeatureEnabled, isProFeatureEnabled)
}
}
What changed:
- the dependency wiring is centralized
- feature previews stay short and readable
Verification:
- the wrapper still passes the same value to the screen
- changing the helper changes all previews consistently
Result
You now have a preview workflow that mirrors production more closely. That makes feature extraction safer because the dependency wiring is visible instead of hidden in app defaults.
Common SwiftUI pitfalls
- Mistake: previewing a screen with no environment injection
Why it happens: the default happens to be good enough
How to fix it: make the preview state explicit - Mistake: putting service logic directly into the preview
Why it happens: it feels faster at first
How to fix it: keep previews declarative and pass in values instead
When NOT to use this
Avoid the workflow if the screen does not depend on injected state. Do not add a helper just to avoid one line of preview setup.
Takeaway
If you remember one thing: A good preview makes dependencies obvious, not optional.
Next steps
This is the right continuation because it keeps the same ownership and environment boundaries visible in the surrounding SwiftUI architecture posts: