Automating iOS Simulator Screenshots With Pretty Good Status Bars Using Xcode 11 by Paulo Fierro

Yesterday I discovered a new command line tool (which ships with Xcode 11) that can clean up iOS Simulator status bars. It works great, but I have automated taking app screenshots using Fastlane and Snapshot — if you haven't you should check it out as its a massive time saver.

Unfortunately there is currently a bug where Snapshot apparently overrides or undoes the status bar change — there is however work underway to add support for this new feature.

Until then, I've come up with what I believe is a clumsy but acceptable workaround. 🤓

I don't want to take screenshots manually. After having used Snapshot for so many years now that seems like going back to the stone age and I already have my lovely UI tests in place to take the screenshots of exactly what I want.

Avoid Town isn't localized at the moment, so its only a case of taking screenshots at two locations on:

  • iPhone SE
  • iPhone 8
  • iPhone 8 Plus
  • iPhone Xs
  • iPhone Xs Max
  • iPad
  • iPad Pro (3rd gen)

That's 2 screenshots for each devices, so 2 * 7 = 14 screenshots. And then I want to take the same screenshot but in dark mode so 28 screenshots in total.

If there were more languages supported that would be 28 per language and you can see that this quickly becomes untenable. So I am going to continue to use Snapshot.

My workaround is as follows, in one Terminal I run fastlane snapshot as normal.

However in a second Terminal I run the following:

while sleep 1; 
do xcrun simctl status_bar 'iPhone SE' override --time '9:41' --dataNetwork 'wifi' --batteryState 'charged' --cellularMode 'notSupported' --wifiMode 'active' --wifiBars 3; 
done

Basically I override the status bar on a particular device every second 😱. This ensures that it also happens while Snapshot is running.

Yep, I know its gross 🤢. And it also means I have to take snapshots of one device at a time. Then I have to stop the script, change the name of the device, and run Snapshot again for that device. Like an animal.

You could of course modify all the Simulators you're targetting in your Snapfile by adding multiple do lines to the while block above, but I did them one by one to verify that the process worked.

The upside is that its mostly automated and because I only have to do it 7 times (for this release) I can live with it. My hope is that by the time I have to do this again the Snapshot issues linked to above will be fixed 🤞

Below is an example of the output created by Snapshot.

Screenshots exported via Snapshot

Screenshots exported via Snapshot

Making perfect Pretty Good iOS Simulator status bars with Xcode 11 by Paulo Fierro

This morning I've been updating the screenshots for the latest update of Avoid Town which adds among other things support for Dark Mode on iOS 13.

I've been using Simulator Status Magic for as long as I can remember to clean up the status bar — this involves removing the Carrier name, setting the battery to 100% and full bars. For some reason this wasn't working, so I started looking into it and found the following:

Xcode 11 beta 4 includes support for perfect status bars without SimulatorStatusMagic! 🎉 Run xcrun simctl status_bar with beta 4 or later installed and rejoice! This project will be going away soon, which is great news.

Looks like the time has finally come to wave goodbye to Simulator Status Magic. Its been a great run and you served us well, thank you for your service 👋😄

The xcrun simctl status_bar tool allows us to override the status bar with several options:

time <string> Set the date or time to a fixed value. If the string is a valid ISO date string it will also set the date on relevant devices.

dataNetwork <string> If specified must be one of 'wifi', '3g', '4g', 'lte', 'lte-a', or 'lte+'.

wifiMode <string> If specified must be one of 'searching', 'failed', or 'active'.

wifiBars <int> If specified must be 0-3.

cellularMode <string> If specified must be one of 'notSupported', 'searching', 'failed', or 'active'.

cellularBars <int> If specified must be 0-4.

batteryState <string> If specified must be one of 'charging', 'charged', or 'discharging'.

batteryLevel <int> If specified must be 0-100.

My first attempt was the following:

xcrun simctl status_bar 'iPhone 11' override --time '9:41' --dataNetwork 'lte' --batteryState 'charged' --cellularMode 'active'

This sets the time to 9:41, using LTE with a full battery. This looks great on iPhone X and later.

Cleaned up status bar on iPhone X

Cleaned up status bar on iPhone X

However, on older devices this still shows the Carrier name.

Carrier name visible on iPhone 8

Carrier name visible on iPhone 8

I really dislike showing the Carrier name and it doesn't look like there is a way of removing it using simctl override, so instead I opted for the following:

xcrun simctl status_bar 'iPhone 11' override --time '9:41' --dataNetwork 'wifi' --batteryState 'charged' --cellularMode 'notSupported' --wifiMode 'active' --wifiBars 3

This sets the time to 9:41, using Wifi with a full battery.

Carrier name now longer visible on iPhone 8

Carrier name now longer visible on iPhone 8

Cleaned up status bar on iPhone X, now using WiFi

Cleaned up status bar on iPhone X, now using WiFi

Personally I prefer showing LTE and bars, but this doesn't appear to be possible without also showing the Carrier name which seemingly we can't remove so this is a fine compromise.

Another option is to vary what we do depending on if the device has a notch or not. I'm currently trying to figure out how to automate this with Fastlane.

Update (Sep 18, 2019): I wrote about how I automated this with Fastlane / Snapshot here.

Moving to SF Symbols by Paulo Fierro

I'm slowly working on replacing existing assets with SF Symbols wherever possible in this app I'm working on. In order to maintain a consistent look on iOS 12 and below I'm replacing the existing 1/2x/3x PNGs with a single PDF, by exporting the SVG from the SF Symbols app into Sketch.

By naming the bundled assets the same as the system names, I can use this handy extension to avoid the "if available" dance every time:

At first I wasn't sure if I wanted to update the older assets, but I think I prefer a consistent look regardless of iOS version even if it does mean a bit of extra work. Moving to PDF assets also means less files to update and hopefully a smaller app size.

I ❤️ cocoapods-binary by Paulo Fierro

I've been using Cocoapods for dependencies for as long as I can remember. Personally I never really got along with Carthage, but some people prefer it.

Over the weekend I came across a post titled "Using CocoaPods with pre-built frameworks instead of source files" which has radically decreased the build time on some of my projects. It describes how you can set up the cocoapods-binary plugin to precompile your dependencies which dramatically reduces the amount of time Xcode needs to build your app.

In two small-to-medium sized apps with 5-10 dependencies I saw upto 10x improvements. A clean build (with nuked derived data) going from about 90 seconds down to 9. 🤯🤯🤯

If you're using Cocoapods you really should try it out.

Making Get to the CHOPPER by Paulo Fierro

Yesterday I uploaded a video from back in October 2015, where Niqui and I surprised our guests that were visiting with a helicopter tour around our lovely island, Grand Cayman.

The video itself was shot on 3 cameras – a GoPro 3 HERO Black, my trusty iPhone 6s Plus with an Olloclip and Niqui’s iPhone 6s. The footage looked good but unless you’re well known with the island its not that easy to figure out where we are at any given point in time.

Luckily Chris was wearing his new Ambit and tracked our flight path. This is exactly what I needed.

The flight path

The idea was to show the map of the flight path with the image of a chopper showing our current position in the lower-right hand corner as the video played.

Enter Motion.

I’m very much a Motion newbie — I’ve used it a couple times with results I’m mostly happy with, but still look quite amateurish. Maybe that’s part of the charm.

Initially I was thinking maybe I could get the flight-path file and parse the GPS coordinates and build something automatically. Turns out its a lot easier to trace the path instead.

I used the flight-path image from Chris as a base and used the bezier tool to trace the path. Once I was happy with that I launched Google Earth, zoomed in to the island to find the right location and took a screenshot. This was the background.

The background

Next I needed a helicopter. Found this lovely, 1987 model from a TurboGrafx-16 game called J.J. & Jeff.

Chopper

Chopper

Perfect.

Motion has a built in behavior called “Motion Path” which I applied to the image. Motion now asked me to draw the path. I’d already done this once so didn’t really feel like doing it again. Much searching later I found that the motion path’s “Path Shape” property was set to the default, “Open Spline”.

"Open Spline", you're not the one that I want

"Open Spline", you're not the one that I want

Changing this to “Geometry” allowed me to provide my existing, traced path as the “Shape Source”. Perfect! I now had my helicopter image moving along the path which was exactly what I was looking for.

There are a few times where the helicopter changed direction and it looked a bit weird for the chopper to fly backwards. Ok, we need a horizontal flip. But there is no way to flip horizontally.

I thought maybe I could set up a keyframe and swap the image with one that was pre-flipped, but that seemed wrong.

Much DuckDuckGo’ing later I found that setting the “Scale X” property to -100% means the same thing as “flip horizontally”. Cool.

Export the movie and we’re done.

Nearly. That looks good, but its a bit boring. It would be much cooler if the helicopter was animated.

I opened up the original image in Pixelmator and copied pixels until I had three states: left blade, both blades with rotated rotor, right blade.

Layers of pixels

Chopped chopper. By no means perfect, but good enough

Chopped chopper. By no means perfect, but good enough

I couldn’t figure out how to animate all of these together so I created another Motion project which lasted 3 seconds, with each of the states above lasting 1 second. Looking back now I should have made it 3 frames instead.

I exported this as a QuickTime movie ensuring that I set “Color+Alpha” in the Render options and set the project background to be transparent.

Export settings

Export settings

No matter what I tried, when I imported this into the main Motion project it wasn’t transparent. I searched and searched but found no solution so in the end I made the background a bright green and used the Keyer filter to get rid of it — that worked like a charm out of the box.

Well mostly. On a certain part of the video the animated chopper had a visible left border.

An annoying visible border

An annoying visible border

To get rid of that I set the Matte Tools’ “Shrink/Expand” to -4.

Matte Tools > Shrink/Expand

This got rid of the border but now the chopper looked a bit thin. Nothing a good dose of drop shadow can’t fix.

Border be gone!

Border be gone!

Looking good. To speed up the slow, 3-second animation I set the timing to 800%. And we’re done!

I exported the movie and brought it into Final Cut Pro X where I scaled it down to 25% and put it in the lower-right corner. At that size it was pretty hard to see the chopper, so back in Motion I scaled the animated chopper up to 200% and also added a Sharpen filter.

Finally, I made the clip as long as the edited shots to make syncing up a little easier and to keep the blades animating at full speed. Otherwise it would have looked odd because I’d have to stretch the 10 seconds out to 3 minutes. Now its actually done.

Back in FCPX land the rest of the video was put together. It was basically two multi-cam clips but of course I hadn’t considered how we were going to sync up the video shot on different cameras. Luckily FCPX can sync up video from multiple sources based on their audio signature. That’s some serious magic.

Color grading was done in ColorFinale which is my new favorite tool for this.

And that's it. It was a hell of a lot of fun making it — I certainly learned a lot, though the most fun, the most fun was definitely being on that chopper.