Why This Matters
Screenshot generation is one of those tasks teams postpone until release week, then panic because one tiny UI change breaks ten marketing images. A better pattern is to treat screenshots as a deterministic build artifact.
Today we are going to build a practical screenshot pipeline for SwiftUI apps that removes manual repeat work.
Step 1: Define scenes as data, not one-off scripts
Think of scenes like camera presets in a studio. You should be able to replay them in any order.
struct ShotScene {
let id: String
let route: String
let locale: String
let colorScheme: ColorScheme
}
let scenes: [ShotScene] = [
.init(id: "home-light-en", route: "/home", locale: "en", colorScheme: .light),
.init(id: "home-dark-en", route: "/home", locale: "en", colorScheme: .dark),
.init(id: "wordbook-ja", route: "/wordbook", locale: "ja", colorScheme: .light)
]
Step 2: Force deterministic UI state before rendering
If data is random at render time, your screenshots are random too. Seed your demo data and freeze time-sensitive UI where possible.
@MainActor
func makePreviewStore() -> AppStore {
let store = AppStore()
store.session.userName = "Demo User"
store.wordbook.items = ["context", "pipeline", "latency"]
store.stats.streakDays = 14
return store
}
Step 3: Batch render with explicit destinations
Render commands should be boring and repeatable. A build machine should produce the same output as your laptop.
#!/usr/bin/env bash
set -euo pipefail
SCHEME="MyApp"
OUT_DIR="./artifacts/screenshots"
mkdir -p "$OUT_DIR"
xcodebuild test \
-scheme "$SCHEME" \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-resultBundlePath "$OUT_DIR/result-bundle"
Step 4: Add a visual regression check
Store known-good images and compare them with the current run. Fail fast when layout drifts.
func hasUnexpectedDrift(_ baseline: UIImage, _ candidate: UIImage, tolerance: Double) -> Bool {
let diffRatio = PixelDiff.ratio(baseline, candidate)
return diffRatio > tolerance
}
Pitfalls People Hit First
- Mixing production API calls into screenshot test flows.
- Using user-generated content that changes each run.
- Saving images with non-deterministic filenames.
Verification Checklist
- Two consecutive runs produce identical files for the same commit.
- Locale and dark-mode shots are both generated every run.
- Regression check fails when text wraps unexpectedly.