Can the real Slim Space Please stand up? by Paulo Fierro

Today I ran into an interesting problem where some of our unit tests were failing locally, after upgrading to macOS Sonoma and Xcode 15.

Apparently there was a subtle change made to date formatting, and I was seeing this error.

11:30 AM is not equal to 11:30 AM

After lots of squinting, I pasted the error message into Sublime Text and spotted the error.

Secret characters lurking in the mist

Fixing the test was straightforward, but it did leave me thinking I should probably write a comment to let future me (and others) know that I wasn't losing the plot.

You can’t see it, but I swear its there!

After chatting to Chris about this, he suggested escaping the Unicode sequence, which is undoubtedly a much better approach and makes it more obvious what is going on.

Much better

So who is Mr. 202f? Apparently its a Narrow No-Break Space and is also used as a group separator, which given the context makes perfect sense.

Automatically formatting and linting Swift by Paulo Fierro

I'm a huge fan of formatters and linters as they enforce style and conventions, making it hard if not impossible to tell who authored a file, function, etc.

For Swift projects I've been using the great SwiftFormat and Swiftlint (both of which can be installed via Homebrew), with my favorite linting and formatting rules.

When working on an Xcode project, I tend to add a Run Script to Xcode's Build Phases which runs Swiftlint and SwiftFormat to lint and format every time you build. While this does slow down your build by a second or two, its not been something I've noticed in my experience.

Recently I've been authoring more and more Swift packages, which do not have a build phase equivalent. I haven't had any luck with the Swift Package Manager plugin system, so I've resorted to using a Git hook, specifically pre-commit.

This is a sample pre-commit that both formats and lints a package. Any errors that occur are redirected to stderr, so if you're using a GUI app for Git (I couldn't live without Tower) any errors are shown in a dialog.

Errors are shown in a dialog

Any errors are shown in a dialog

To add the precommit hook:

  1. save the script to a file called precommit
  2. give the script execute permissions (e.g. chmod u+x precommit)
  3. either manually move it into the .git/hooks folder,
  4. or make a symlink (e.g. ln -s pre-commit .git/hooks/pre-commit)

The next time you commit some code, the script will run and potentially modify some of your files. At that point I tend to amend the original commit with those changes.

When barcodes attack β€” fixing PDF417 detection in Xcode 13 by Paulo Fierro

The bug πŸ›

This past week I came across a fascinating bug, the kind I don't think I've encountered before. I'm working on a project where we use the [Vision framework][0] to detect [PDF-417 barcodes][1].

In order to do that we create a VNDetectBarcodesRequest with its symbologies set to PDF-417, like so:

When using the release candidate for Xcode 13 (build 13A233) which ships with the iOS 15 SDK and Swift 5.5 toolchain, there is now a warning saying that .PDF417 has been renamed to .pdf417.

However, if you change the symbol name to the new, lowercased version and try to run on an earlier version of iOS (e.g. iOS 13) this results in a crash at runtime:

SIGABRT: missing lazy symbol called

As you would expect, the lowercased symbol name works fine on devices running iOS 15.

The Workaround πŸ”¨

Initially I thought, I'll wrap this in an @available conditional and the problem will go away. Shouldn't be necessary but it fixes the problem:

However, we're still using Xcode 12.5 on CI to build and test, and if this is an issue how many more monsters are out there?

It may be wiser to wait until Xcode 13.1 before moving to this new toolchain.

The band-aid 🩹

So to get the best of both worlds and ensure this works on both older and the latest versions of iOS, regardless of your choice of Xcode (limited to 12.5 or 13), I ended up going with the following:

It doesn't feel that clean, but it does work. πŸ€·β€β™‚οΈ

ο£Ώ Feedback: FB9659813.

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.