While working on updating iOS screenshots for the App Store recently, I discovered that simctl status_bar is still broken. And unfortunately, I do not expect it to be fixed any time soon.

In This Series

This post is part of a series about overriding status bar display settings in the iOS simulator.

  1. Overriding status bar display settings in the iOS simulator
  2. A script to automate overriding iOS simulator status bar values
  3. Fully automating perfect status bar overrides for iOS simulators with Nine41
  4. Workaround: simctl status_bar broken for iOS 16 simulators
  5. Workaround: Xcode simctl status_bar is still broken for iOS 17 simulators

I’ve written before about automating perfect status bar overrides for the iOS simulator using my utility, Nine41. The current bug in simctl status_bar not only prevents Nine41 from working, but also breaks every third-party tool like fastlane, SimGenie, etc. that offer a status bar override feature — because those all use simctl status_bar under-the-hood.

* * *

Previously, I realized you could still use the iOS 16.0 simulators and earlier. However, if your app is targeting iOS 17 and above — my current situation since dropping iOS 16 — then this workaround will no longer suffice because your app no longer runs on iOS 16.

The only workaround I have found is to use our good old friend, SimulatorStatusMagic — which Dave wrote years ago before simctl status_bar existed. (It is now maintained by other great folks.) In my earlier post, I tried to guess (incorrectly) what might have caused the breakage in simctl status_bar, but the project’s README explains the problem:

1) Injecting into Springboard (Required on iOS 17+)

tl;dr Running build_and_inject.sh booted will apply a default status bar to the running simulator. Replace “booted” with a simulator UDID to target a specific simulator.

As of iOS 17, the API used by SimulatorStatusMagic is not accessible to processes other than Springboard. So, in iOS 17+ we need to inject SimulatorStatusMagic into the Springboard process itself, which we do by building it as a dynamic library, and then updating Springboard’s launchd configuration to load our dynamic library.

Running build_and_inject.sh will do all of this for you. If you want to change anything about the values used in the status bar, you will need to update DynamicLibrary/main.m.

And there’s our answer: “As of iOS 17, the API used by SimulatorStatusMagic is not accessible to processes other than Springboard.” Presumably, this is why simctl status_bar no longer works. From the outside, it sometimes feels like teams within Apple have little to no communication with each other, resulting in bugs like this. Or, perhaps this is just the typical dysfunction that manifests with very large organizations.

* * *

Luckily, the status bar overrides applied using SimulatorStatusMagic will persist across simulator launches. Thus, you only need to run build_and_inject.sh booted once for each simulator. I decided to just do this for all of my simulators so that I never have to worry about this. (Honestly, there’s really no reason for them to ever show the current date and time.)

If you fully reset a simulator using Erase All Content and Settings..., then you will need to re-run the script for that simulator. Once you apply the overrides to the simulators, you can use whatever screenshot automation tools you prefer (Xcode UITests, fastlane snapshot, etc.) and the status bar overrides will remain. If you need this functionality on a CI service, you are in for a bit more work, but it is possible.

This is far from ideal, but at least we have a workaround that allows us to continue creating great screenshots.