Running Swift apps
peach swift opens a Swift app in Peach and keeps it running while you edit.
It runs in the foreground: it compiles your app, opens a preview, watches your
sources, and stops everything it started when you press Ctrl+C.
Swift support is in beta. See the iOS overview for what compiles today and what does not.
What you can open
A SwiftPM package
Pass a directory that contains Package.swift. Its executable target is the
app; library targets it depends on compile as their own modules. A minimal
SwiftUI app is one file beside the manifest:
An Xcode project or workspace
Pass an .xcodeproj, an .xcworkspace, or a directory that holds exactly one
of them. A directory with a workspace opens the workspace. A directory with two
workspaces or two projects is refused, and the error lists them so you can pass
one. Peach reads both the classic project.pbxproj format and the JSON
project.xcproj format that Xcode 27 writes.
Peach reads the project as data. It never runs Xcode, xcodebuild or your
build phases, so a run script that generates sources must have run already.
From the project it builds what Xcode builds for your app target:
- +the target’s Swift sources and copied resources, including synchronized folders
- +framework and library targets in any project of the workspace that the app depends on or links, each as its own module
- +local Swift packages the workspace references
- +remote Swift packages, compiled from source at the revisions
Package.resolvedpins. Peach fetches them into.build/peachunder the workspace. A remote package with no pin is refused; resolve packages in Xcode once to createPackage.resolved.
If you open a project that belongs to a workspace in a parent directory, Peach uses that workspace, because it holds the local packages and the pins.
Choosing a target or scheme
A project with one application target needs no flag. With more than one, Peach refuses and lists them; pick one:
To build the way a scheme’s Run action does, pass the scheme instead. Peach
uses the scheme’s runnable target and its build configuration. It finds shared
schemes (xcshareddata/xcschemes) and your own (xcuserdata/*/xcschemes) in
the workspace and each project.
An unknown scheme is refused with the list of schemes that exist. So is a
scheme that runs no application target. --scheme and --target cannot be
combined, and neither applies to a SwiftPM package.
Build configuration and settings
Without a scheme, Peach builds Debug when the project declares it, as Xcode’s
Run action does, and otherwise the project’s default configuration. Settings
resolve the way Xcode resolves them: project level, then target level; each
level’s base .xcconfig (with its #includes) before its own settings;
$(inherited); and conditional keys such as [sdk=iphonesimulator*] and
[config=Debug].
- +
SWIFT_ACTIVE_COMPILATION_CONDITIONSand-Dflags inOTHER_SWIFT_FLAGSset your Swift#ifconditions, so#if DEBUGfollows your project. - +Swift packages build in debug for
Debugand in release for any other configuration, as Xcode builds them. - +
$(SETTING)references inInfo.plistexpand against the same settings. An undefined setting expands to nothing, as in Xcode. - +A base configuration file the project names must exist. If your repository asks you to create one in a setup step, do that before opening the project.
- +Editing an
.xcconfigor project file rebuilds.
Where it compiles
On your machine (default)
By default your source never leaves your machine. Peach compiles with the
swift.org toolchain release it pins (Swift 6.4.0) and its WebAssembly SDK.
If either is missing, peach swift stops and prints the exact commands that
install them for your platform. On macOS the toolchain installs into your home
directory beside Xcode without replacing it.
An installed Peach also needs the prebuilt Peach Swift distribution, which it
downloads, verifies and caches on first use. During the beta that distribution
is available by invitation: set PEACH_SWIFT_ENDPOINT to the endpoint you were
given. The distribution ships for macOS on Apple silicon and Linux on x64 and
arm64.
On Contrast (--upload-source)
With --upload-source, Peach sends your app’s Swift sources to the Contrast
compiler instead, and sends each watched edit as you save. You need no local
Swift toolchain. Passing the flag is your consent to that upload, and it
requires a signed-in session (peach login) or a PEACH_API_KEY.
Uploaded compiles accept Swift sources only. A project with C or C++ targets, or a vendored native library, needs the local toolchain. Results are cached by content, so an unchanged set of files never compiles twice.
Editing while it runs
Save any watched file and Peach compiles the change and swaps it into the
running app. The terminal prints Swift artifact: <hash> for each new build.
- +View state carries over. A view whose position in the view tree did not change keeps its
@State: counters, toggles, text, navigation paths, the selected tab and a presented sheet. State a property cannot carry (for example a custom type) rebuilds from its initial value, as SwiftUI does after a structural change. - +Pushed screens stay pushed when you change the views above the link that pushed them.
- +Literal edits skip the compiler. When a save only changes a string, number or bool literal inside a view body, Peach applies the new value to the running app without compiling, the way Xcode Previews does. The terminal prints
Swift literals: N changed. Any other change compiles. Uploaded compiles always compile. - +Errors keep the session alive. A failed compile prints the compiler’s file, line and column against your original source, and Peach keeps watching. Fix the error and the next save resumes the app with its state.
The first compile must succeed before the preview opens. If it fails,
peach swift exits with the error and closes everything it started. The first
build compiles every dependency, so it takes longer than later edits. After
that, an edit recompiles only the changed file and relinks.
Reading the missing API report
When your app imports modules Peach does not provide, peach swift prints one
list of every such module, the files that import it, and for a package Peach
cannot compile, the reason. When compilation fails, it ends with the APIs Peach
lacks, each with its error count and the files that use it. That report is
the most useful thing to send us when you tell us what your app needs.
Flags
| Flag | Effect |
|---|---|
--target <name> | Run this Xcode application target when the project has more than one. |
--scheme <name> | Run the target and configuration of this scheme’s Run action. Not combinable with --target. |
--upload-source | Compile on Contrast instead of this machine. Uploads the project’s Swift sources and watched edits. Needs peach login or PEACH_API_KEY. |
--device <model> | Open on this simulated device, for example iphone-17-pro or ipad-pro-13. An unknown model is refused with the list of known ones. |
--headless | Open the preview without a visible browser window. |
--no-open | Keep compiling and serving the app without opening a preview. A running Peach lists it in the Swift app on the dock. |
--host-port <port> | Pin the local port the Swift preview host listens on. Otherwise Peach picks a free one. |
--driver <playwright|desktop|electron> | Choose which browser driver opens the preview. |
--cdp-port <port> | Pin the Playwright debugging port. |
peach swift serves your app only to your own machine; the preview host
answers loopback requests only.
Once it is running
The app runs in a normal Peach simulator, so every other command works on it:
take a screenshot with peach screenshot, record with peach record, change
location or permissions, or drive it in a test. See Testing,
Device state and
Sharing.