# simctl

By [Mattt](https://nshipster.com/authors/mattt).
Published 2018-11-26.
Canonical URL: <https://nshipster.com/simctl>

At the heart of Apple’s creation myth is the story of Steve Jobs’ visit to Xerox PARC in 1979. So inspired by the prototype for the Alto computer was he, that — like [Prometheus](https://en.wikipedia.org/wiki/Prometheus) — Steve Jobs would steal _fire from the gods_ and bring it to the masses by way of the Lisa and Macintosh computers a few years later.

Like so many creation myths, this one is based on [dubious historical authenticity](https://web.stanford.edu/dept/SUL/sites/mac/parc.html). But this is the story we like to tell ourselves because the mouse and graphical user interfaces were indeed revolutionary. With a mouse, anyone can use a computer, even if they aren’t a “computer person”.

…though, if you _are_ a “computer person”, you probably use keyboard shortcuts for your most frequent operations, like saving with `⌘``S` instead of File > Save. More adventurous folks may even venture into the Utilities folder and bravely copy-paste commands into Terminal for one-off tasks like bulk renaming. This is how many programmers get their start; once you see the power of automation, it’s hard not to extend that to do more and more.

Which brings us to today’s subject: If you make apps, you spend a good chunk of your time in Xcode with Simulator running in the background. After [exploring the basic functionality of Simulator](https://help.apple.com/simulator/mac/current/#/deve44b57b2a), you might feel compelled to automate the more time-consuming tasks. The `simctl` command-line tool provides you the interface you need to use and configure Simulator in a programmatic way.

---

If you have Xcode installed, `simctl` is accessible through the `xcrun` command (which routes binary executables to the active copy of Xcode on your system). You can get a description of the utility and a complete list of subcommands by running `simctl` without any arguments:

```console
$ xcrun simctl
```

Many of these subcommands are self-explanatory and offer a command-line interface to functionality accessible through the Simulator app itself. However, the best way to understand how they all work together is to start with the `list` subcommand.

## Managing Simulator Devices

Run the `list` subcommand to get a list of the available runtimes, device types, devices, and device pairs:

```console
$ xcrun simctl list
-- iOS 12.1 --
    iPhone 5s (CC96B643-067E-41D5-B497-1AFD7B3D0A13) (Shutdown)
    iPhone 6 (7A27C0B9-411A-4BCD-8A67-68F00DF39C1A) (Shutdown)
    iPhone 6 Plus (A5918644-8D46-4C67-B21E-68EA25997F91) (Shutdown)
    iPhone 6s (1AB5A4EB-2434-42E4-9D2C-42E479CE8BDC) (Shutdown)
…
```

Each device has an associated [UDID](https://nshipster.com/uuid-udid-unique-identifier/). You pass this to any of the `simctl` subcommands that take a device parameter.

One such subcommand is `boot`, which starts up the specified device, making it available for interaction:

```console
$ xcrun simctl boot $UDID
```

> Once you’ve booted a device, you can pass the string `"booted"` instead of the UDID for subcommands that operate on a particular device. If you pass `"booted"` when multiple devices are booted, `simctl` chooses one of them.

To see the booted simulator in action, open the Simulator app with the `open` command, like so:

```console
$ open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/
```

You can see which devices are booted by redirecting the output of `list` to a `grep` search for the term “Booted”:

```console
$ xcrun simctl list | grep Booted
iPhone X (9FED67A2-3D0A-4C9C-88AC-28A9CCA44C60) (Booted)
```

To isolate the device identifier, you can redirect to `grep` again to search for a UDID pattern:

```console
$ xcrun simctl list devices | \
    grep "(Booted)"         | \
    grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})"
9FED67A2-3D0A-4C9C-88AC-28A9CCA44C60
```

Pass the `--json` or `-j` option to `list` and other subcommands to format output as JSON. Having a structured representation of resources like this is convenient when you’re writing scripts or programs for managing simulators.

```console
$ xcrun simctl list --json
```

```json
{
  "devicetypes" : [
    …
    {
      "name" : "iPhone X",
      "bundlePath" : "\/Applications\/Xcode-beta.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform\/Developer\/Library\/CoreSimulator\/Profiles\/DeviceTypes\/iPhone X.simdevicetype",
      "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-X"
    },
    …
    ]
}
```

When you’re finished with a device, you can shut down and erase its contents using the `shutdown` and `erase` subcommands:

```console
$ xcrun simctl shutdown $UDID
$ xcrun simctl erase $UDID
```

> If you work a lot with Simulator (and have done so for a while), you’ve probably accumulated a sizable cache of old devices — many of which may not be usable in the latest versions of iOS. If this sounds familiar, running the following command could save you a few gigabytes of disk space:
>
> ```console
> $ xcrun simctl delete unavailable
> ```

## Copying & Pasting Between Desktop and Simulator

It’s easy to feel “trapped” when using Simulator, unable to interact directly with the OS as you would any other app. Recent versions of Simulator have improved this by allowing you to automatically synchronize pasteboards between desktop and simulated device. But sometimes it’s more convenient to do this programmatically, which is when you’ll want to break out the `pbcopy` and `pbpaste` subcommands.

These subcommands work like their macOS counterparts, except that they act on the pasteboard of a simulated device:

- `pbcopy` reads from standard input and copies it to the pasteboard of the device in the Simulator
- `pbpaste` writes the pasteboard contents of the device in the Simulator to standard output

For example, if you wanted to copy the contents of a file on your Desktop to the pasteboard of a simulated device, you’d use the `pbcopy` subcommand like so:

```console
$ xcrun simctl pbcopy booted < ~/Desktop/file.txt
```

After running this command, the contents of `file.txt` will be used the next time you paste in Simulator.

### Opening URLs in Simulator

You can use the `openurl` subcommand to have Simulator open up the specified URL on a particular device.

This URL may be a webpage, like [this one](https://nshipster.com/simctl/):

```console
$ xcrun simctl openurl booted "https://nshipster.com/simctl"
```

…or it can be a custom scheme associated with an app, such as `maps://` for Apple Maps:

```console
$ xcrun simctl openurl booted maps://?s=Apple+Park
```

### Add to Photo Stream

We’ve all been there before: developing a profile photo uploader and becoming all-too-familiar with the handful of photos of flora from San Francisco and Iceland that come pre-loaded on Simulator.

Fortunately, you can mix things up by using the `addmedia` subcommand to add a photo or movie to the Photos library of the specified simulator:

```console
$ xcrun simctl addmedia booted ~/Desktop/mindblown.gif
```

Alternatively, you can drag and drop files from Finder into the Photos app in Simulator to achieve the same effect.

### Capture Video Recording from Simulator

Sure, you can use the global macOS shortcut to capture a screenshot of a simulator (`⇧``⌘``4`), but if you intend to use those for your App Store listing, you’re much better off using the `io` subcommand:

```console
$ xcrun simctl io booted screenshot app-screenshot.png
```

You can also use this subcommand to capture a video as you interact with your app from the simulator (or don’t, in the case of automated UI tests):

```console
$ xcrun simctl io booted recordVideo app-preview.mp4
```

> If you’re a detail-oriented person, you likely have strong feelings about what shows up in the status bar for your app screenshots. With [SimulatorStatusMagic](https://github.com/shinydevelopment/SimulatorStatusMagic), you can ensure that it’s always 9:41 AM and your device has 100% battery and full cellular and Wi-Fi signal bars.

## Setting Simulator Locale

One of the most arduous tasks for developers localizing their app is switching back and forth between different locales. This process typically involves manually tapping into the Settings app, navigating to General > Language & Region, selecting a region from a long modal list, and then waiting for the device to restart. Each time takes about a minute — or maybe longer if you’re switching back from an unfamiliar locale (你懂中文吗？)

But knowing how simulators work in Xcode lets you automate this process in ways that don’t directly involve `simctl`.

For example, now that you know that each simulator device has a UDID, you can now find the data directory for a device in `~/Library/Developer/CoreSimulator/Devices/`. The simulator preferences file `data/Library/Preferences/.GlobalPreferences.plist` contains a number of global settings including current locale and languages.

You can see the contents of this using the `plutil` command:

```console
$ plutil -p ~/Library/Developer/CoreSimulator/Devices/$UDID/data/Library/Preferences/.GlobalPreferences.plist
```

Here’s what you might expect, in JSON format:

```json
{
  "AddingEmojiKeybordHandled": true,
  "AppleLanguages": ["en"],
  "AppleLocale": "en_US",
  "AppleITunesStoreItemKinds": [ … ],
  "AppleKeyboards": [
    "en_US@sw=QWERTY;hw=Automatic",
    "emoji@sw=Emoji",
    "en_US@sw=QWERTY;hw=Automatic"
  ],
  "AppleKeyboardsExpanded": 1,
  "ApplePasscodeKeyboards": ["en_US", "emoji"],
  "AKLastIDMSEnvironment": 0,
  "PKKeychainVersionKey": 4
}
```

You can use the `plutil` command again to modify the preferences programmatically. For example, the following shell script changes the current locale and language to Japanese:

```bash
PLIST=~/Library/Developer/CoreSimulator/Devices/$UDID/data/Library/Preferences/.GlobalPreferences.plist

LANGUAGE="ja"
LOCALE="ja_JP"

plutil -replace AppleLocale -string $LOCALE $PLIST
plutil -replace AppleLanguages -json "[ \"$LANGUAGE\" ]" $PLIST
```

You can use this same technique to adjust Accessibility settings (`com.apple.Accessibility.plist`), such as enabling or disabling VoiceOver and other assistive technologies.

To get the full scope of all the available settings, run `plutil -p` on all property lists in the preferences directory:

```console
$ plutil -p ~/Library/Developer/CoreSimulator/Devices/$UDID/data/Library/Preferences/*.plist
```

---

At least some share of the credit for the popularity and success of iOS as a development platform can be given to the power and convenience provided by Simulator.

To be able to test your app on dozens of different devices and operating systems is something that we might take for granted, but is an order of magnitude better than the experience on other platforms. And the further ability to script and potentially automate these interactions makes for an even better developer experience.
