Render a SwiftUI #Preview to PNG from the command line, using the build products of a real iOS project.
The intended loop is simple: edit a view and its preview in your preferred editor, run suip, then inspect the resulting PNG. Once the application target has a Debug simulator build, suip can render the current preview without opening Xcode's preview canvas or rebuilding the application.
suip is editor-agnostic. It is intended for previewing SwiftUI work without depending on Xcode's preview canvas and as a foundation for future editor integrations.
Rendered by suip from the tracked
ShowcasePreview.swift
fixture.
A SwiftUI preview is not a standalone image description. Its body may depend on internal target types, file-scoped helpers, compilation conditions, package dependencies and code that only exists in the latest edit. It must also run inside an iOS environment rather than directly on macOS.
suip combines the edited source with the target's existing Debug build. It compiles a small per-edit dynamic library, loads that library into a reusable host inside an iOS Simulator, and snapshots the resulting SwiftUI view.
suip has three cooperating pieces:
- Mac CLI - discovers the project and owning target, extracts the preview, invokes
swiftc, manages caches and controls the simulator. - Simulator host - loads the per-edit dynamic library with
dlopen, calls its exported preview entry point and writes the rendered view to PNG. - Shared contracts - JSON request and response messages plus a dynamic
PreviewProtocollibrary that carries the SwiftUI view across the library boundary.
The application target remains responsible for compiling the wider project and its dependencies. suip borrows those build products while recompiling the source needed to reflect the current preview edit.
- An Apple silicon Mac running macOS 13 or newer
- A Swift 6.2 or newer toolchain to build suip, as included with Xcode 26 or newer
- Xcode 16 or newer selected when rendering; Xcode 26 satisfies both toolchain requirements
- An iOS project or Swift package that declares an iOS deployment target
- A Debug build of the selected target for an arm64 iOS Simulator
- A booted iOS Simulator
For an Xcode target, previews also require its Debug dynamic library. Targets with ENABLE_DEBUG_DYLIB = NO or MERGED_BINARY_TYPE are not supported.
From a checkout of this repository:
swift build -c releaseThe executable is written to .build/release/suip.
The suip checkout and the application being rendered can live in separate directories. Before changing to the application directory, save the executable's absolute path in the current shell:
SUIP="$(pwd)/.build/release/suip"Build the application target for an iOS Simulator, boot a simulator, and run suip from the application's project root:
cd /path/to/YourApp
"$SUIP" render YourApp/ProfileCard.swiftrender is the default subcommand and may be omitted. If the source belongs to several targets, select one explicitly:
"$SUIP" render YourApp/ProfileCard.swift \
--target YourAppA successful render prints the resolved target information followed by the PNG path:
target: YourApp
module: YourApp
ios: 17.0
sdk: /path/to/iPhoneSimulator.sdk
/Users/you/Library/Caches/suip/<project-hash>/dylibs/<content-hash>/preview.png
Run suip render --help for the current command syntax.
suip recognizes these project markers at the top level of the current directory:
- An
.xcworkspace - An
.xcodeproj - A
Package.swift
Workspaces may reference Xcode projects and Swift packages. Source ownership is resolved from target membership, including Xcode filesystem-synchronized groups. Use --target when more than one target owns the source file.
Project discovery does not walk up through parent directories. Run suip from the directory containing the project marker, and pass a source file below that directory.
suip currently expects SwiftPM's native-build layout:
.build/arm64-apple-ios-simulator/debug/
Swift 6.4 and later use a different swiftbuild layout by default. Until suip supports that layout, build standalone packages with the native build system:
swift build \
--triple arm64-apple-ios-simulator \
--sdk "$(xcrun --sdk iphonesimulator --show-sdk-path)" \
--build-system nativeSwiftPM currently prints a deprecation warning for the native build-system option. The command still produces the layout suip requires while support for the default layout is being developed.
Local Swift packages reached through an Xcode-rooted project are not yet supported.
suip is a work in progress. The end-to-end rendering pipeline works, but the command-line and integration contracts are not yet stable.
Current limitations include:
- A source file must contain exactly one
#Preview. - Sibling extensions generally work. If copied extensions from several files import modules that define the same name, compilation can fail with an ambiguous-name diagnostic.
- suip cannot detect when edits in other target files are newer than the Debug build. Rebuild the application target before rendering a preview that depends on those edits.
- After rebuilding a target, the long-running simulator host can retain the previous target image. Stop it with
xcrun simctl terminate booted dev.oddmagnet.suip, then run suip again. - Swift 6.4 and later's default SwiftPM
swiftbuildlayout is unsupported. - Local packages inside Xcode-rooted projects are unsupported.
- Output is human-readable only. Structured JSON output and distinct failure-category exit codes are not implemented.
- Custom size, scale, appearance and locale traits are not implemented.
The rendering pipeline is exercised end to end against focused fixture projects and real iOS project builds. This covers project discovery, target selection, dynamic library compilation, dependency scanning, simulator communication, caching, source slicing and sibling extensions.
The tracked fixtures/App project provides a reproducible end-to-end check. With an iOS Simulator already booted, run these commands from the suip repository root:
swift build
xcodebuild \
-project fixtures/App/MinimalApp.xcodeproj \
-scheme MinimalApp \
-destination 'generic/platform=iOS Simulator' \
-configuration Debug \
build
(
cd fixtures/App
../../.build/debug/suip render MinimalApp/ContentView.swift
)There is not yet a conventional test target. It was deferred while the pipeline was being established and remains part of the project cleanup work rather than a substitute for end-to-end verification.
Project conventions and the rules for AI-assisted work are documented in AGENTS.md.
The product code is handwritten. AI assistance is used to read and review code, explain behavior, help plan work, surface tradeoffs and summarize maintainer-written changes. It does not write product code; test fixtures and throwaway spikes are exceptions. The maintainer decides what enters the repository, and commits summarized by AI are marked accordingly.
- Isolate generated sources from different sibling files.
- Select among multiple previews in one source file.
- Report when borrowed build products are stale.
- Refresh the simulator host after a target rebuild.
- Support SwiftPM's current default build layout.
- Discover local packages inside Xcode-rooted projects.
- Add structured output and distinguishable exit codes.
- Build a Neovim integration.
- Add custom rendering traits after the initial editor integration.