A macOS CLI that renders SwiftUI #Preview blocks to PNG images — giving AI coding assistants visual feedback on the views they build.
snapview is intentionally preview-driven. It renders discovered #Preview entries, not arbitrary View types with guessed state.
When you ask an AI to build a SwiftUI view, it has no way to see what the result looks like. snapview bridges that gap: render a preview to disk, hand the PNG back to the AI, iterate visually.
- macOS 14+
- Xcode 15+
- A project with at least one test target
- The test target must either generate its own
Info.plist(GENERATE_INFOPLIST_FILE = YES) or point to a validINFOPLIST_FILE - Views must have
#Previewblocks
You can either download the latest macOS release asset from GitHub Releases or build from source.
Source install:
git clone https://github.com/Abdo-codes/SnapView.git
cd SnapView
swift build -c release
install "$(swift build -c release --show-bin-path)/snapview" /usr/local/bin/snapview# One-time setup if the project does not already have snapview files
snapview init --scheme MyApp
# Repair or inspect the current project state
snapview doctor --scheme MyApp
# Default day-to-day workflow
snapview watch --scheme MyApp
# Print or regenerate the gallery page path
snapview gallerywatch is the default local loop. It bootstraps missing or stale prepared artifacts, keeps the persistent host aligned with the latest build, rerenders previews after Swift changes, and refreshes .snapview/gallery.html.
Use the integration smoke script to verify a real project end to end:
scripts/integration-smoke.sh \
--scheme MyApp \
--project /path/to/MyApp.xcodeprojUse --workspace /path/to/MyApp.xcworkspace instead of --project when the app is built from a workspace. Add --watch when you want the script to wait for one successful preview refresh before exiting. Set SNAPVIEW_BIN to point the script at a specific snapview binary instead of the default .build/debug/snapview.
If you want the explicit primitives, they still exist:
snapview prepare --scheme MyApp
snapview host start --scheme MyApp
snapview render ContentView --scheme MyApp
snapview render-all --scheme MyApp
snapview listsnapview render <ViewName>matches discovered preview entries by backing view name, preview body, or explicit preview name.snapview render-allrenders every discovered#Previewentry in the project.snapview listshows the exact preview coverage snapview can render.- If a screen has no
#Preview, snapview will not render it. Add a#Previewblock for every screen or state you want in the output set.
Example:
#Preview("Dashboard") {
DashboardView(store: .preview)
}
#Preview("Dashboard - Empty State") {
DashboardView(store: .previewEmpty)
}The example above produces two renderable outputs. If SettingsView has no preview, it will not appear in snapview list or snapview render-all.
- Doctor — inspects project health and reports actionable fixes for preview coverage, test-target setup, prepared-state drift, host drift, and output fallback.
- Prepare — scans project sources for
#Previewblocks, generates the full registry, and runsxcodebuild build-for-testing. - Host — runs a long-lived XCTest host inside the simulator for near-instant repeated renders.
- Render — prefers the running host when present, otherwise falls back to
xcodebuild test-without-building. Config is passed via JSON files because env vars do not forward throughxcodebuild. - Gallery — persists
.snapview/gallery.jsonand.snapview/gallery.htmlso the local output has a stable surface. - Watch — polls Swift files under the app source root, debounces change bursts, re-prepares when needed, restarts stale hosts, rerenders the preview set, and refreshes the gallery.
| Command | Description |
|---|---|
snapview init --scheme <Scheme> |
One-time setup. Adds renderer to test target. |
snapview doctor --scheme <Scheme> |
Inspect project health and print suggested fixes. |
snapview gallery |
Print or regenerate the local gallery page path. |
snapview prepare --scheme <Scheme> |
Build test artifacts for fast renders. |
snapview host start --scheme <Scheme> |
Start the persistent renderer host. |
snapview host status |
Show the persistent host status. |
snapview host stop |
Stop the persistent host. |
snapview watch --scheme <Scheme> |
Run the local preview studio loop as Swift files change. |
snapview render <ViewName> --scheme <Scheme> |
Render #Preview entries that match a named view or preview name. |
snapview render-all --scheme <Scheme> |
Render every discovered #Preview block. |
snapview list |
List all discovered #Preview blocks that snapview can render. |
snapview clean |
Remove the .snapview/ output directory. |
| Flag | Description |
|---|---|
--rtl |
Render in right-to-left layout direction |
--locale <id> |
Set locale (e.g. ar, fr-FR) |
--scale <1|2|3> |
Screen scale factor |
--device <name> |
Device display name |
--simulator <id> |
Simulator UDID or name |
snapview writes PNGs to .snapview/ in the project root when it can. It also maintains:
.snapview/gallery.jsonas the local manifest.snapview/gallery.htmlas the generated gallery page
If the destination directory is not writable, snapview falls back to the verified runtime output path and records those PNG paths in the gallery instead of failing the render. Add .snapview/ to .gitignore if you don't want to track renders.
Common recovery paths:
snapview listshows fewer screens than you expected: snapview only renders discovered#Previewblocks. Add explicit previews for each screen or state you want rendered, then rerunsnapview prepare.snapview watchis your default loop, but it exits immediately: runsnapview doctor --scheme <Scheme>first.watchcan repair stale preparation state by running its ownprepare, but it still stops for real project errors such as missing previews or broken test-target setup.build-for-testingfails because the test target has noInfo.plist: setGENERATE_INFOPLIST_FILE = YESon the test target, or pointINFOPLIST_FILEat a real plist.snapview initconfigures generated test targets this way, but older hand-made test targets may need repair.renderorrender-allprints a warning about not being able to copy into.snapview: use the runtime output path printed by snapview. The render succeeded; only the final copy-back failed.render-allorwatchstill shows old previews after you changed preview files: rerunsnapview prepare.watchrestarts stale hosts automatically after a successful prepare, but an already-running manual host may still needsnapview host stopandsnapview host start --scheme <Scheme>.watchkeeps retrying the same broken edit: it should not.watchnow settles on the failed snapshot and waits for the next file change before trying again. If it still loops, restartwatchand rerunsnapview doctor --scheme <Scheme>to check for drift outside the watched source root.renderfails after switching projects, schemes, or test targets: the prepared metadata is stale. Rerunsnapview prepare --scheme <Scheme>.
More detail: Troubleshooting Guide
Contributions are welcome. Start with CONTRIBUTING.md for local setup, verification expectations, and PR scope.
For sensitive reports, use the private process in SECURITY.md instead of opening a public issue.
snapview is released under the MIT License.
prepareis the slow build step;host startremoves most of the repeated XCTest startup cost, but the first prepare is still expensive.- snapview does not auto-instantiate arbitrary SwiftUI views. Add
#Previewblocks for every screen or state you want rendered. watchuses a polling file snapshot loop in v2. It optimizes for determinism and testability over instant filesystem notifications.- Custom fonts may fall back to system fonts.
.navigationTitleand other navigation chrome do not render.@Previewablemacros are not supported.UIViewRepresentablecontent renders blank.