Introducing visionOS Fridays

Design Notes Diary

I’m starting something new to help me get my apps ready for visionOS. Over the summer, while I was preparing my apps for iOS 17, I essentially stopped working on visionOS work because it was more important that I focus on the more pressing concern of getting things ready for September. However, now that’s all behind me I am able to begin return to my work on visionOS.

However, exactly how to do that is not necessarily straightforward. I could just continue to work heavily on visionOS. But realistically I also need to continue making forward progress on my main apps that are shipping right now. Especially because the timing of the visionOS release is so ambiguous. It’s awkward to be working towards something that you don’t have a definite date for. Apple keeps saying that it’ll release in “Early 2024”, but that could mean January or that could mean May, and depending on which one of those things it is but you have a pretty dramatic impact on the amount of work I’m able to do on other projects between now and then.

So the compromise idea that I’ve come up with is to start regularly working on visionOS, but in a limited window each week. Specifically I’m going to start working on visionOS every Friday with something I’m gonna call “VisionOS Fridays”, or for the Spanish speaking, alliteration liking folks “VisionOS Viernes”.

That way I can continue to make meaningful progress, but shouldn’t allow it to impact or impinge on my ability to ship good regular updates to the apps that are out in the store right now. Hopefully this will put me in good shape for when visionOS does actually launch. Hopefully Apple will give us a bit more of a specific date sometime early next year at which point I can easily switch to giving it my full attention to get it finished.

Starting Over

While I’ve done it a lot of work on visionOS so far since it was announced to WWDC, including going to one of the in-person Labs and experimenting with my ideas. I’ve also discovered that because I was built my first version of the app using the earliest form of the Xcode tools there were a lot of issues when I went to try and upload my binary to App Store Connect. I think something went funny when I added the visionOS target to the project and so now when I try to upload the app with visionOS support App Store Connect gets very grumpy.

So it seemed like a good idea to throw away that branch where I’ve been working and instead add support for visionOS in a clean branch (based on the latest version of Widgetsmith) using the latest tools (Xcode 15.1 Beta 2). After a little bit of experimentation it seems like the tooling has improved meaningfully from June and so this will better set me up for success down the road.

I’ll then go back and re-integrate all the code I wrote in the old branch into this newly clean starting point. So I’m not throwing away all the work I did over the summer but instead just moving into a stable environment.

Checking the Checkbox

Adding visionOS support to an existing iOS project is as easy as checking a box.

I just start by telling Xcode that I’d like it to target visionOS and then the app will in theory start to run on a Vision Pro.

However, in reality checking that box is just the start of a rather monumental project to make the app compatible with visionOS. There are dozens of frameworks and methods which aren’t available on visionOS coming from iOS, so anytime you currently mention one of these you’ll now get an error.

For an app like Widgetsmith which deeply integrates with WidgetKit this is a bit of a disaster to then disconnect from.

I’ll spare you all the gory details (and instead just show the highlight lessons), but just to get the app to compile again required hundreds of changes to 82 files.

Approaches to compatibility

Many of these changes are relatively straightforward. Things like changing any of my references to WidgetKit to be wrapped in a conditional logic to exclude it from visionOS.

But of course there was a reason I was importing WidgetKit in the first place, so then I have to go through and workout how I can shim things to get them working again.

Sometimes this takes the form of something like this:

Where I know that a particular view which currently requires WidgetKit just gets stubbed out and replaced with an empty view. This works reasonably well in cases where whole parts of the app just won’t make sense on visionOS (in this case Home Screen widgets).

In other spots things can get pretty awkward where a particular view will be shared between iOS and visionOS. In these cases I can’t simply exclude it. This is particularly gnarly when it involves using SwiftUI modifiers which are only available on iOS. For example the .widgetURL method which I use for handling links from widgets.

In cases like this the “easy” approach is to stub over the missing method on the new platform.

Taking this approach means that the widgetURL method is now available to the compiler when run on visionOS but simply operates as a “no-operation”. This will work and is an approach which I’ve used to great affect on other projects…but I know full well that I’m setting myself up for future pain later on. If Apple does eventually add widgetURL to visionOS I’ll have a bit of a challenging compatibility problem.

The other approach I can take is to instead hide way this incompatibility inside of a new proxy method.

Using this approach I introduce a new method (here compatibleWidgetURL), which I use instead of directly using the missing method. This means that if widgetURL is later added I can much more easily maintain compatibility because I’ll just change this method to switch between them.

This is nearly always the “right” way to handle this kind of system integration work. It is a bit of a pain and makes the code a bit more verbose but ultimately that is way better than coding myself into a corner later.

Another example of handling compatibility between iOS and visionOS is with features which just don’t appear on Vision Pro. For example Haptics aren’t supported for a head mounted device (for good reason!), and so my references to the UIImpactFeedbackGenerator don’t work on visionOS. Here I take the approach of then extracting this out into a new wrapper class which then can either perform the haptic or not based on the device.

This wrapper will again preserve lots of options for the future should some form of haptic equivalent become available on visionOS. I would then alter this method to provide the new functionality.

Conclusions

This first day of “VisionOS Friday” was a bit underwhelming in terms of flashy features but ultimately it was vital to provide myself with the ability to move forward with the project. Performing a clean integration of my previous work using the latest tools means that I can now move forward with a visionOS compatible project with confidence that when it does come to sharing it with TestFlight or the App Store that I won’t be caught out with weird project compatibility issues.

Next Friday I’ll be diving in properly to adding features to the app again.

David Smith