Let me tell you, I was super excited when I saw the first Marzipan presentation.
And who wasn’t?
We could finally make Mac apps without rewriting the entire UI. Yes please!
And it was a natural logical next step, since Apple already had UIKit working on Mac inside the iOS simulators.
So today we’re going to check out the Marzipan. I mean Catalyst. Funny, it's still called ‘Marzipan’ in Apple's code.
Initially, I was going to port our AppSpector SDK to Catalyst. It would be great to allow Catalyst developers debug remotely and monitor their desktop apps like AppSpector allows mobile developers do with their iOS and Android apps.
What we need is a proper app for the SDK testing. Some real app. Not just a sample app from Apple.
My pick is an application called Morning Pages.
I was involved in the development of this app, and it would be cool to see how it woudl work on a Mac. This app was already using AppSpector, so it was a perfect candidate. Network requests, CoreData, intensive logging and all the rest of the stuff that we needed for our test was already there.
Time to check the checkbox
Let's check the magic checkbox and find out how our app would look on a Mac.
Damn! But hey, nobody said it’d be easy.
A bunch of libs for analytics like Heap, Google Analytics, Fabric and Crashlitics are distributed as static libs. We need to remove them all until they get updated for Catalyst.
You might ask, why can't we link against libraries that have a slice for the iOS simulator? It's the same x86. The problem is that Mac Catalyst is a different platform than the iOS Simulator, and linker refuses to use the iOS Simulator slice when linking binary for macOS returning the following message:
libGoogleAnalytics.a(GAITrackerImpl.o), building for UIKitForMac, but linking in object file built for iOS Simulator
I had to remove the AppSpector SDK for the same reason: we have not updated it for Catalyst yet. We will go over the SDK migration in one of the upcoming articles.
Next roadblock, please! The iOS SDK API’s are not available on macOS.
Below you can see the errors I received in relation to the set of Keychain API being unavailable in Mac Catalyst. I had to temporarily remove it until KeychainAccess pod is updated.
Depending on your codebase this step could be a piece of cake as long as you only need a couple of changes done. It could become hell if those API’s are crucial for your application.
A quick note on the compatibility
When reading through Apple documentation, one might notice this availability table on the right side:
That’s just platform availability for the selected API, you might say.
True, but why do we have the ‘Mac Catalyst’ among other 4 OSs?
It’s not an OS, it’s just a bunch of iOS SDK frameworks ported to macOS. How does it affaect developers struggling to build their iOS apps for macOS?
In the pre-Catalyst times, if you made a decision to have a macOS app alongside the iOS one, one of the most important choices you would have to make is which part of the codebase to reuse and how to do it?
One approach I can think of, is to extract all business logic to a separate framework, make abstractions for platform-specific API’s, then build a separate UI and link the logic framework. That should do it.
Separation of platform specific things from the universal things made it possible. Modifying business logic parts involves only your ‘core’ part which is the same across all platforms. UI modifications should be implemented for each platform. Approach Apple suggests treating the Mac Catalyst (which, in fact, is a part of the iOS SDK) as a separate UI-only meta-platform.
What it really means is:
- We need one more separation inside our UI code. One part, written with the Mac Catalyst, could be used on all platforms, while platform-specific UI parts should be kept separate and be wrapped in some availability macro. NSWindow-related things like Dock, TouchBar, NSStatusItem and some others live here.
- All non-UI code we would like to reuse, should be built for one extra platform, because the Mac Catalyst is a separate binary type.
In the end, I was finally able to build the project. It took me only 10 minutes to make it work:
Everything seems to be working fine, except for a few minor things:
UI and Autolayout
Some of the 3rd party UI components you might use, may not work as expected with the Mac Catalyst.
For example, MorningPages onboarding just stopped responding to gestures on macOS. Autolayout seems to be working fine, except you have runtime warnings on conflicting constraints. Of course, in the perfect Universe you would never have any issues. Your project would always compile without warnings, and you’d have a ‘-Werror’ compile flag.
But sometimes you need to roll up your sleeves and implement a few “dirty hacks” to make your UI work as advertised. Doing that will most certainly bite you back on macOS. UI that rendered perfectly on iOS, but triggered Autolayout runtime warnings looked completely screwed up on my Mac.
Apps built with the Mac Catalyst can’t have the same bundle ID as their iOS counterparts.
Xcode adds ‘uikitformac’ prefix to your bundle ID. And breaks a lot. I’m talking about InApp purchases, CloudKit, and anything that depends on bundle ID. You can use ‘DERIVE_UIKITFORMAC_PRODUCT_BUNDLE_IDENTIFIER’ setting and set to ’NO’ to disable this behaviour, but you still need another bundle ID. Good luck syncing your user CloudKit databases.
You are limited to the latest OS versions (iOS 13 and macOS 10.15). It means if you still want to support users on older OS versions (and I bet you do), you will end up having availability macro spread across your entire codebase.
I understand, after reading this post it might seem as if I’m disappointed with Catalyst. I am not. We all should understand that any new tech introduced by Apple is a Beta, and will remain beta for the next couple years. Unfortunately.
I suggest you think of Catalyst as a direction. An ecosystem is moving in and we need to prepare our codebases and ourselves as developers to start shipping apps to all Apple platforms at once.
In my next post I’ll be taking a look at adopting AppSpector SDK to be used in Catalyst apps.
So stay tuned!