Skip to main content

Destinations and devices

Every command that builds needs to know three things: which scheme, which configuration, and where the result should run. That last one is the destination, and it's the one you'll change most often, between a simulator, your Mac, and a phone on your desk.

Seeing what's available​

sweetpad devices lists everything runnable in one place, each with the specifier xcodebuild wants:

$ sweetpad devices
* simulator · iPhone 15 (iOS 26.5) [booted]
platform=iOS Simulator,id=F92801F8-9EE7-4AF6-8D9E-8D8D8F5A06A3
macOS · My Mac (macOS)
platform=macOS
simulator · iPhone 15 Plus (iOS 26.5)
platform=iOS Simulator,id=3AE2296B-7BEF-45F6-AFDF-90D2AD082738
…

The list is ordered most-used-first for the current project, so the destination you actually work with sits at the top. A * marks the one this project remembers; [booted] marks simulators that are already running.

Each pool has its own narrower list when that's what you want:

sweetpad simulator list # simulators only, with UDIDs
sweetpad device list # paired physical devices, with how each one connects
sweetpad device info # connect to a physical device and check that it's ready

Simulators covers the rest of that group: booting, screenshots, push payloads, permissions, and managing the pool.

Saying where to run​

--on takes a human description and resolves it against that live list:

You writeYou get
--on "iPhone 16 Pro"The simulator or device with the closest name.
--on bootedWhatever simulator is already running.
--on macYour Mac, for macOS schemes.
--on deviceYour connected physical device.
--on ios / --on visionosThe newest simulator of that platform. watchos and tvos work too.
--on work-phoneAn alias you created yourself (see below).
--on <UDID>That exact simulator or device.

It works on every command that builds or runs:

sweetpad run --on "iPhone 16 Pro"
sweetpad build --on mac
sweetpad test --on booted

Matching is fuzzy, so you don't have to reproduce Apple's exact naming. When a description fits more than one thing, SweetPad names the candidates instead of picking for you:

$ sweetpad build --on "17 pro"
error: --on "17 pro" is ambiguous (iPhone 17 Pro (26.5), iPhone 17 Pro Max (26.5), iPad mini (A17 Pro) (26.5)) — be more specific

An exact name always wins over a longer one that contains it, so --on "iPhone 15" picks the iPhone 15 rather than complaining about the Plus and the Pro.

The choice SweetPad remembers​

Most of the time you won't pass --on at all. The first build in a project with no destination settled gets an interactive picker, ordered most-used-first with booted simulators marked, and the answer is remembered for that project. Every later command reuses it.

sweetpad status shows what's currently in effect and where each value came from:

$ sweetpad status
/path/to/SweetpadCIApp.xcodeproj (project)
scheme SweetpadCIApp (remembered)
configuration Debug (remembered)
destination platform=iOS Simulator,id=F92801F8-… (remembered)
run `sweetpad app run` to build and launch

That parenthetical is the useful part. When a build does something you didn't ask for, it names the layer responsible: a flag, an environment variable, a config file, a remembered answer, or auto-detection.

note

One-off destinations aren't remembered. A --destination you typed, and the --mac and --device shortcuts, apply to that command only, so a quick check on your Mac doesn't quietly become the default for the next week.

A --scheme you type isn't remembered either. When the next command has no scheme and can't ask, its error names the scheme your last build used, or lists the project's schemes, so you can pass --scheme again or keep one with sweetpad context set scheme.

Changing what's remembered​

sweetpad context is how you edit the remembered values. Don't hand-edit the state file; this is the supported way in.

sweetpad context show # everything currently saved for this project
sweetpad context select # re-answer scheme, configuration, and destination
sweetpad context select destination # re-answer just one, interactively
sweetpad context set scheme MyApp # set a value with no prompt (scripts and CI)
sweetpad context remove destination # forget one value
sweetpad context remove --all # forget the whole project context

context set accepts scheme, configuration, sdk, destination, and target.

A separate context for tests​

Testing keeps its own remembered destination, so you can develop against one simulator and run the suite on another. Add --testing to any of the commands above to act on it:

sweetpad context select --testing
sweetpad context set destination 'platform=iOS Simulator,name=iPhone SE (3rd generation)' --testing

Naming a destination​

A UDID is not something anyone wants to type twice. Give one a name, then use the name anywhere --on is accepted:

sweetpad context alias work-phone 00008110-000559182E90401E
sweetpad run --on work-phone
sweetpad context alias work-phone --remove

Physical devices​

A paired iPhone or iPad shows up in sweetpad devices and in sweetpad device list. Both show how it connects to your Mac: usb or wifi, and not paired if it hasn't trusted this Mac yet.

$ sweetpad device list
Iphone 13 (iPhone 13, iOS 26.6) [wifi]
00008110-000559182E90401E

In JSON, a device entry also has devicectl's own connection, transport, and pairing values. An idle device reads connection: "disconnected" even when it works fine, because xcodebuild opens the connection when it needs one.

Target it by name, by UDID, or with the device shorthand when there's only one:

sweetpad run --on device
sweetpad run --on "Iphone 13"
sweetpad run --device-id 00008110-000559182E90401E

Device builds have to be signed, which is the one place xcodebuild usually needs more from you than SweetPad asks for. Pass the signing settings through:

sweetpad app install --on device -- -allowProvisioningUpdates DEVELOPMENT_TEAM=ABCDE12345

If your project always needs them, put them in sweetpad.toml once instead. See Extra xcodebuild arguments.

Checking that a device is ready​

A listed device isn't necessarily ready. It also has to be unlocked, trusted, and in Developer Mode before xcodebuild can reach it. sweetpad device info connects to the device and checks each of those:

$ sweetpad device info "Iphone 13"
Iphone 13 (iPhone 13, iOS 26.6)
00008110-000559182E90401E
pairing paired
connection connected (wifi)
developer mode enabled
developer disk not mounted
lock locked
boot booted
devicectl The developer disk image could not be mounted on this device.
not ready: Iphone 13 is locked; unlock it so Xcode can start its development services

The last line is the verdict: ready to build and run, or the first thing to fix. The command exits 1 when the device isn't ready, and -o json reports the same facts plus ready and reason fields.

With no argument, it checks the only paired device. It waits up to 10 seconds for the device to answer, and --timeout changes that.

macOS​

macOS schemes run natively, with no simulator involved:

sweetpad run --on mac
sweetpad run --mac # the same thing

build, test, and test build also take --mac, which means the same as --on mac:

sweetpad test --mac
sweetpad build --mac

The macOS destination is also where the CLI's Mac-only verbs apply: app screenshot captures the app's window, and app ui reads and drives it through accessibility.

For an iOS app, --on mac builds what xcodebuild -destination platform=macOS builds. An app that sets SUPPORTS_MACCATALYST = YES builds for Mac Catalyst, into Debug-maccatalyst. Any other iOS app builds "Designed for iPad" with the iOS device SDK (iphoneos), into Debug-iphoneos, and so do the frameworks its scheme builds. settings show --on mac reports the same settings, and the app commands look for the bundle there.

A scheme can build targets for more than one platform, such as an iOS app and a macOS helper. Each target that can't run on the destination builds for its own platform, as it does in xcodebuild: under an iPhone simulator, the macOS helper still builds for macOS, into Debug.

The raw escape hatch​

--destination takes xcodebuild's exact specifier, with no fuzzy matching:

sweetpad build --destination 'platform=iOS Simulator,name=iPhone 16 Pro'
sweetpad build --destination 'platform=iOS,id=00008110-000559182E90401E'
sweetpad build --destination 'platform=macOS'

When run or another app command installs from a name= specifier, it picks the simulator the way xcodebuild does. The name must match exactly, and so must the platform and the OS= when the specifier gives one. If several simulators still match, a booted one wins. Xcode keeps an iPhone 16 Pro for every iOS runtime you install, so add OS= when you have more than one.

--on and --destination are mutually exclusive, so pick one per command. Each of them is also exclusive with --mac, and on the app commands with --device and --device-id too.

Prefer --destination in CI. Fuzzy matching against whatever simulators a runner happens to have installed is a liability, and a pinned specifier fails loudly instead of quietly building for the wrong thing. sweetpad devices prints a copy-paste-ready specifier for everything it lists.

Every targeting flag also has an environment-variable twin, so a pipeline can set the context once instead of on every command:

export SWEETPAD_SCHEME=MyApp
export SWEETPAD_DESTINATION='platform=iOS Simulator,name=iPhone 16 Pro'
sweetpad build && sweetpad test

Which setting wins​

When the same value is set in more than one place, the most explicit one wins:

flag > environment variable > config.toml > sweetpad.toml > remembered answer > auto-detect

sweetpad status always reports which of those a value came from, and sweetpad help destinations has the same material available offline.