Testing Forward Compatibility in Xcode

At the time of this writing, we are in that betwixt-and-between period leading up to the release of iOS 14; Xcode 12, Big Sur, and iOS 14 itself are still in beta, but things could start to go final any day now.

If you’ve got existing apps, there are various ways to prepare for that moment. Some folks are busy recompiling their apps against the iOS 14 SDK and maybe even adopting some of its cool new features.

But others have a different problem; they’re asking: “How will iOS 14 affect my existing app that’s already been compiled against iOS 13 in Xcode 11?” You probably have an app like that. The one that’s in the App Store right now. The one that’s on users’ devices right now. Eventually, users are going to upgrade their devices to iOS 14; when they do that, what will happen to your app?

Forward Compatibility is a Thing

I call this kind of problem forward compatibility. It doesn’t get as much attention as backward compatibility, which can be a hard problem and can involve you in all kinds of difficulties; but forward compatibility is a real thing too, and it presents problems of its own.

Apple tries pretty hard to provide forward compatibility. On my personal iPhone, there are apps that haven’t been updated in years. Nevertheless, they work fine. That’s because new versions of iOS try to host those older apps so that things behave pretty much as they did when those apps were compiled. Apple tries not to impose new features on your older app. And you can see why. If they didn’t, a huge proportion of existing apps could break overnight when a new iOS version is released. Obviously, Apple doesn’t want that.

Nevertheless, every once in a while, Apple does something in a new system version that breaks your existing app that’s compiled for an older version.

For example, here are a few things in my iOS 12 apps that broke when running under iOS 13:

  • Deselecting all of a segmented control’s segments by setting its selectedSegmentIndex to UISegmentedControl.noSegment stopped working.

  • Customizing a navigation bar’s back indicator with setBackIndicatorImage(_:transitionMaskImage:) broke because Apple accidentally swapped the image with the mask.

  • A bar button item’s background image stopped being resized vertically to fit the bar button item, causing a lot of my bar button items to look terrible.

Things like that can happen any time we cross a major iOS version boundary. So why wait until after the new iOS version goes final and your users install it? You’d probably like to get a general sense of what’s about to happen before iOS 14 goes final and a significant proportion of your users upgrade their devices.

But how?

Peering Into the Future

Your first thought might be to install the iOS 14 beta on a device. But that won’t help. Xcode 11 can’t deal with a device that’s running iOS 14. In the Devices & Simulators window, it complains: “The current device configuration is unsupported.” If you actually try to build and run from Xcode 11 onto an iOS 14 device, you get an alert with a rather misleading message asking you to reconnect the device.

Okay, so you can’t do that. So what about downloading Xcode 12? It has iOS 14 simulators. So you can run your app using Xcode 12 on an iOS 14 simulator, right?

But not so fast. The word “run” conceals the fact that you have just recompiled your app against the iOS 14 SDK. That’s wrong. You don’t want to test how your app behaves when it’s recompiled for iOS 14; you want to test how your app behaves on iOS 14 when it’s not recompiled for iOS 14!

So here’s the problem. You’ve got a viable iOS 14 “device” sitting right in front of you: a simulator from Xcode 12. And you’ve got the ability to compile against the iOS 13 SDK because you’ve also got Xcode 11. But never the twain shall meet. Xcode 12 has an iOS 14 simulator but not the iOS 13 SDK, and Xcode 11 has the iOS 13 SDK but no iOS 14 simulator. Catch-22!

Well, I’m here to tell you that if you have both Xcode 11 and Xcode 12, you can use Xcode to compile your app against the iOS 13 SDK and run it on an iOS 14 simulator.

One Weird Trick

This is what you do. (Are you all sitting comfortably?)

  1. Launch Xcode 11 and build your app with any simulator as the destination — but don’t run it, just build it.

  2. Still in Xcode 11, find the built app under Products in the Project navigator. Select it and choose File > Show in Finder.

  3. Quit Xcode 11.

  4. Launch Xcode 12.

  5. Choose Xcode > Open Developer Tool > Simulator.

  6. In the Simulator application, choose File > Open Simulator > iOS 14 > [some simulator].

  7. When the simulator window is running and displaying the springboard, switch to the Finder and drag the built app onto the simulator window.

That’s it. Your app, built against the iOS 13 SDK, is now loaded onto an iOS 14 simulator. You can now launch it and study it to see how it behaves.

Another Weird Trick

Okay, that was cool. But it isn’t as cool as it might have been, because you are not actually running under Xcode, so you can’t pause at breakpoints, read logging messages in the Xcode console, and so forth. That would be impossible, right?

Wrong! Here’s how to do that; it’s almost like what we just did, only backward:

  1. Launch Xcode 12.

  2. Choose Xcode > Open Developer Tool > Simulator.

  3. In the Simulator application, choose File > Open Simulator > iOS 14 > [some simulator].

  4. Quit Xcode 12.

  5. Launch Xcode 11.

  6. Build and run your app onto the iOS 14 simulator that is now sitting open.

That’s right: Xcode 11 can see the Xcode 12 Simulator application’s open simulators, and you can pick one of those as the destination when you build and run.

A Different Kind of Breakage

Forward compatibility of your existing app isn’t the same as forward compatibility of your project. When you recompile under a new SDK, you expect some breakage. That, in part, is why you are recompiling the app. You want to adopt new features; you haven’t adopted them yet, so you slam into them instead.

There was quite a bit of that sort of breakage when people recompiled their iOS 12 apps with the iOS 13 SDK, remember? Return with me now to those thrilling days of one year ago:

  • All my presented view controllers went from being fullscreen to the new “card” style, partially covering the presenting view controller with a black space at the top. These view controllers can be dismissed without tapping a Cancel button, and when they are dismissed, the expected viewWillAppear call to the presenting view controller never arrives (because it never disappeared in the first place).

  • Dark mode! A lot of my app’s interface was suddenly potentially illegible because the user could turn the background black.

In short, iOS 13 came along and broke my app, with no change and no effort on my part. But that was just what I expected. I went through my code and made my presented view controllers compatible with the new “card” style. I went through my interface and made it compatible with dark mode. And all was well.

The point is, those things could only happen if I purposely recompiled for iOS 13. Meanwhile, the existing version of my app, compiled for iOS 12, was unaffected by those changes. Its presented view controllers were still fullscreen, and dark mode didn’t make any of its interface go dark, even on iOS 13.

Nevertheless, my existing app, running under iOS 13, did encounter other issues, as I explained earlier. Sometimes new systems bring changes that do affect older apps. That’s the sort of thing you want to try to catch before the new system overtakes your app. And now you know how.

You Might Also Like…

Updating Your App to iOS 14

Each year, like clockwork, for better or worse — sometimes, in my opinion, very much for worse — Apple releases a new version of iOS. We’re all used to the forced march of the calendar by now. In June, WWDC is held, and the new version appears in beta. In September, it goes final. And …

Updating Your App to iOS 14 Read More »

    Sign Up

    Get more articles and exclusive content that takes your iOS skills to the next level.