Catalyst after one year
Next week it’s time for WWDC 2020, which marks the 1 year anniversary of Catalyst. A good time to look back at what it has brought us, which I’m gonna do in this pretty long post. Lets start by looking at what Apple is promising with Catalyst:
Now it’s incredibly simple to start building a native Mac app from your current iPad app. With Mac Catalyst, your apps share the same project and source code so you can efficiently convert your iPad app’s desktop-class features, and add more just for Mac.
This is something that I had been waiting for since I started the development of Rigelian by the end of 2017. The app is modularised in such a way that all the code that communicates with a player is in a separate framework, and that framework is completely neutral to the platform (iOS, macOS, tvOS, watchOS). That means that creating a Mac version of Rigelian would ‘only’ require to create the front-end, but still that would be a significant amount of work. With Catalyst that could potentially be much less.
So lets have a look at the numbers. The total number of yours spent on the development of Rigelian since its inception is about 1200 hours. Of that around 65 hours are spent on the Mac version, which is a little over 5%. That in itself is quite a good number: in less than 2 full weeks of work (in reality this was split over the last 6 months) a reasonably sized iPad app can be turned into a Mac app. Also in terms of ongoing development of the app, all new functions almost automatically are available on both platforms which is a great bonus.
Conclusion: yes, with Catalyst you can create and maintain a macOS app in a fraction of the time it would take to create a native app.
How native does an app based on Catalyst feel
The next question is: what do you get for those 5%? An iPad and a Mac are very distinct devices, and Mac apps look and feel quite different from iPad apps. Now here I was lucky, as the iPad UI of Rigelian translates very well to macOS. It’s basically a single screen app with drill down capabilities is look at the details of genres, artists, albums etc. Apple’s own music app is a single screen app as well.
Since the app is well suited for macOS, it’s then a matter of fine-tuning the UI. The human interface guidelines https://developer.apple.com/design/human-interface-guidelines/ios/overview/mac-catalyst/ provide a good starting point for that. In the case of Rigelian, it meant creating a menu bar and moving the bottom toolbar to the top. Also context menus were migrated to UIMenu. Also prior to starting I moved to dynamic type for all text in the app. Without that labels and other text may look appear too small on the Mac.
Multiple windows can also be supported, but there are some caveats around that on which I’ll come back in the next section. Controls like text-boxes, checkboxes and buttons are in iOS style which looks a bit off. Popup menus on regular buttons are not possible, UIMenu only works for right-click context menus.
Conclusion: for an app like Rigelian it’s possible to make it look pretty mac-like, but there are areas where you see its origin shine through. Your mileage may vary.
The first thing I have to say is that most things just work, especially if the code is mostly up-to-date. Almost all open source frameworks that are used by Rigelian (about 30, including some of myself to communicate with music players) can be used without issues. This includes popular frameworks like RxSwift, Kingfisher, Alamofire and Realm. One package would not compile under Catalyst, which I could easily solve and hand back through a pull request.
I already migrated most of the dependencies to Swift Package Manager at an earlier stage, that makes dependency management overall much simpler.
From the Apple provided frameworks, everything around networking, animation, user preferences, avplayer and storekit is available and works out-of-the-box. Although support for shared subscriptions across the macOS and iOS app is only available since March 2020 (iOS 13.4). These recent changes also make it possible to share the same app-id which makes delivery to the AppStore easier.
Stability is excellent, I notice no difference to the iOS version of the app, the number of reported crashes is equally low. So is performance, with the remark that I run the app on the latest Macbook Pro on which every app should perform well.
Conclusion: mostly things just work which is great.
What’s not so good
First, if you want to support anything older than iOS 13 things become more complicated. The earlier example of context menus through the use of UIMenu works only on iOS 13, so I had to provide a separate solution for context menus under iOS 12 and older. This required significant rework in many places of the app.
Related to this is the use of multiple windows. This is made possible through UIWindowScene which is, you guessed it, only available under iOS 13. So all popup windows are presented as form sheets, which is not very Mac like.
On the framework side, firebase analytics which I use to get some statistics on how the different functions in the app are used, is not available for Catalyst. It’s unclear if it ever will be.
With regards to documentation, I find it so-so. Documentation from Apple is quite limited, and while there are some basic tutorials available I had trouble finding information how to do some things. But that might also because I was looking for info on functions that simply don’t exist.
Conclusion: side-by-side support for iOS 12 and Catalyst complicates things. Some frameworks are not available (yet).
Apple states in one of its WWDC talks of last year that a better iPad app makes a better Mac app. That is definitely the case. When you stick to standard api’s there’s a good chance that the behaviour on Mac will be what a user would expect. As just mentioned, supporting older iOS versions makes this more difficult. Which brings me to the next point.
While in theory it should be possible to build everything from a single target in XCode, I opted for two separate targets, one for the regular iOS build for iPhone and iPad, and a separate one for the macOS build. Separate targets provide more flexibility in terms of build settings, and also which frameworks are included. For example the Catalyst target excludes firebase analytics.
Also the Catalyst version of Rigelian starts from its own storyboard, based on a UISplitView with a custom navigation controller in the detail view controller. It is probably possible to have just one storyboard for both the iPad and macOS version, but I found it easier to just have an own, clean entry point.
I also ended up with a total of 29 #if target(macCatalyst) checks to drive different functionality on the iOS and macOS platforms.
Hopes for the future
I’m interested what improvements to Catalyst will be announced next week at WWDC. I hope they will make it easier to support multiple windows, that some of the UI elements like checkboxes will be rendered as macOS native controls and that regular popup menus are made available.
Also I hope / expect that the rumoured migration to ARM processors in the Mac will be easy for apps based on Catalyst, as they already can compile for and run on the iPad.