Bringing Adaptivity to Mac Catalyst

Introduction

Adaptivity is an app which helps developers and designers visualise how iOS’s Size Classes and margins for layout, readable content and the safe area look on real devices and how they change with respect to orientation, iPad Slide Over/Split View and Dynamic Type size changes.

In the full paid version of the app, Adaptivity (A), there are also screens to explore System Colors, System Images, System Materials and Table Views. On iPadOS 13, this version supports multiple windows. Adaptivity (B) is a free, feature-limited, version. Adaptivity (C) is an iPhone-only version of Adaptivity (B) which demonstrates how iPhone-only apps appear on iPad.

Image for post
Image for post
Adaptivity on 11" iPad Pro on iPadOS 13: Main Window with Popover modal presentation in split view multitasking with System Colors auxiliary window

There is much more information and many screenshots on my website that describe all of Adaptivity’s features on iOS and Adaptivity for Mac. The iOS and Mac versions of Adaptivity (A) are a universal purchase. Buying one version allows the other to be downloaded at no extra cost using the same Apple ID.

This article summarises the work it took to bring Adaptivity to the Mac. It is not intended to be a tutorial or guide to porting an app to Mac Catalyst. If you are planning porting your own apps to Catalyst this should give you an idea of the kinds of changes that will be required. I do go in to more detail on some issues that I had.

Quite deliberately, I chose to make as few Mac-specific customisations as possible so that Adaptivity would better demonstrate the default behaviour when porting an iPad app to Catalyst — what you get “for free”. There was still a lot more work than just checking the box in Xcode.

Resources

See the following resources for more information on developing with Mac Catalyst.

Enabling Catalyst Support

Xcode 11.4 or later is required to build universal Mac Catalyst apps. Apple’s article Creating a Mac Version of Your iPad App explains how to enable support for Mac Catalyst:

To add support for Mac, open your Xcode project and select the iOS target that you want to configure. In the General tab, under Deployment Info, select the Mac checkbox.

Fixing the Deprecations

Building for Catalyst is equivalent to building for iOS with a minimum deployment version of iOS 13. Anything referred to in code that was deprecated in iOS 13 or earlier will generate a warning when building for Catalyst. Even when using availability checks to support different iOS versions, deprecated methods or properties in the else clause will trigger warnings. Availability checks are a runtime feature, not a compile time feature. It does not matter that the else clause will never actually be executed when run on Mac, the compiler still sees that code when building and will generate warnings about using deprecated APIs.

The iOS version of Adaptivity has a minimum deployment version of iOS 11. Demonstrating behaviour on different iOS versions is an important feature of the app, so I wanted to keep that support. Conditional compilation was required to avoid referring to the deprecated APIs when building for Catalyst.

  • UIViewControllerPreviewDelegate for 3D Touch previews. Adaptivity already had support for iOS 13's UIContextMenuInteractionDelegate
  • UISearchController’s dimsBackgroundDuringPresentation was replaced with obscuresBackgroundDuringPresentation in iOS 12
  • Pre-iOS 13 scene workflow support for external displays. Trying to set the screen property for a UIWindow is deprecated in iOS 13. All the code for handling screen added/removed notifications would never be used and could be conditionally compiled away on Catalyst
  • Adaptivity uses a library of helper classes that is shared with many of my other apps. That library still supported iOS 8 and included usage of some long-deprecated classes such as UIAlertView, UIActionSheet, UIPopoverController and UILocalNotification. I took a detour to update that library to use a minimum iOS deployment version of iOS 11. Which required a further detour to update a few of my older apps which used the library to also drop support for earlier iOS versions.

Unavailable APIs

Some iOS APIs and features are simply not available on Mac Catalyst:

  • Add/Edit Voice Shortcuts for Siri. I removed the Siri Shortcuts row and sub-screen from the app’s Settings screen.
  • Other app targets: Today Widget, Notification Content Extension, iMessage Extension, Intent Extension, Intent UI Extension, Watch app. Xcode automatically excludes these targets from the Catalyst version.
  • The Dynamic Type screen allows the font to be changed on iOS 13. It uses UIFontPickerViewController to allow the user to choose a custom font, including those from other font provider apps (i.e. the com.apple.developer.user-fonts entitlement is included). This class is not marked as unavailable on Mac, but nothing happened when trying to present the font picker. I simply removed that option when run on Mac. There was another problem related to this feature which is discussed later.

Customising Adaptivity Features

Some Adaptivity features aren’t really useful on Mac or needed wording changes:

  • The Keyboard screen is pointless on Mac
  • Without a Notification Content Extension there’s no point generating local notifications
  • The Status Bar and Adapt Model Presentation settings were removed. Mac doesn’t show a status bar and the main view is always a regular size class so no adaptions are ever needed when presenting other view controllers.
  • Some user-facing text in table view footers and alert messages needed customising. For example, tapping (instead of long pressing) the “Context Menu” button in the main screen shows an alert “Use a long press to show a context menu” on iOS 13. For Mac it reads “Use a control click or right click to show a context menu”.
  • The iOS app’s More Actions options are shown in a table view which allows an auxiliary window to be opened by dragging a row to the edge of the screen. The Mac version does support the same auxiliary window types, but only choosing an item from the File→New menu or (as on iOS) tapping the ⊕ button in the top left of a view of the same type that has been modally presented on top of a Main Window.
  • Auxiliary windows on iOS have a close button which destroys the scene. On Mac I hide that button because the red traffic bar light in the window title can be used instead.

Mac-Specific Changes

There were some changes I made to the app to make Adaptivity more Mac-like but I tried to keep them to a minimum.

The app’s screens which show dimensions have a Point/Pixels segmented control to select the units for the size labels. Mac Catalyst apps use a scaling factor of 1 / 1.3 (≈77%) to reduce the size of apps. When showing sizes in pixels, the app takes account of this content scaling. For example, 1300 points will be 1000 pixels on a non-retina screen and 2000 pixels on a retina screen.

Image for post
Image for post
Adaptivity App Icon in Xcode 11.4

On iOS, tapping into the search bar in the System Colors and System Images screens dims the background. This looked a bit strange on Mac because the search bar doesn’t take over the navigation bar as on iOS:

Image for post
Image for post
Image for post
Image for post
Adaptivity on Mac with dimming (left) and non-dimming (right) search controller behaviour

The Mac Catalyst section of the Human Interface Guidelines has many recommendations including:

Don’t tint buttons in table rows. In your iPad app, you use a tint to show that buttons in table rows are active, but in macOS, tinted buttons in table rows look out of place.

Image for post
Image for post
Image for post
Image for post
Adaptivity on Mac with tinted (left) and non-tinted (right) table view row buttons

Several changes were made to the default menu items. I removed the Edit and Format menus. I replaced the File→New menu item with a sub-menu to allow new instances of the primary Main Window (which retained the ⌘-N keyboard shortcut) or auxiliary windows to be created. Adaptivity does not include a Help Book so I replaced the “Adaptivity Help” menu item with items to link to my web site and the App Store page for the app.

Image for post
Image for post
Adaptivity on Mac menu bar with custom File→New sub-menu

The standard Mac About window uses the CFBundleName Info.plist entry for the app’s name. This defaults to $(PRODUCT_NAME), exposing the internal name of the target (“AdaptivityA” with no space in my case). I had originally hoped to refer to the Mac version as simply “Adaptivity” (with no suffix), but the universal app mechanism forces the same name to be used for all supported platforms. I need the A suffix on iOS to distinguish it from the B and C variants. I chose to change CFBundleName to “Adaptivity” to make the About window cleaner. iOS uses CFBundleDisplayName when present, so is unaffected by this change.

I also added NSHumanReadableCopyright to the Info.plist to provide a copyright message.

Image for post
Image for post
Adaptivity on Mac About window

Issues

There were a number of issues I came across when porting Adaptivity to Mac Catalyst and had to work around.

When running Catalyst apps from Xcode which support multiple windows, opening and closing windows often crashes. Somebody had already reported this in the developer forums. I found that running the app directly (not through Xcode) worked around the issue.

The default transparent navigation bars on iOS 13 sometimes caused strange display bugs. This seemed to be affected by switching focus between apps and whether the navigation controller was presented in a separate auxiliary window or presented modally.

Image for post
Image for post
Image for post
Image for post
Adaptivity on Mac navigation bar display problems (left) and forced opaque background (right)

I never really fully understood the problem so I forced an opaque background to be used at all times:

Force opaque navigation bar background color

The Human Interface Guidelines suggest using a new sidebar style in UISplitViewController for a more Mac-like appearance. When I tried this, it occasionally worked but usually I would see some table view rows styled incorrectly. I stopped trying to use the sidebar background style.

Image for post
Image for post
Image for post
Image for post
Adaptivity on Mac navigation bar split view controller sidebar bacgkround style problems (left) and none style (right)

The System Materials screen allows the user to choose a custom background image. This would fail if the App Sandbox capability for User Selected File was not set to Read/Write (despite the app only ever reading files).

When I tried to build a notarised version of the app for beta testing I received a warning saying that the Application Category was not configured in the Info.plist file. I added an entry with the value “Developer Tools”.

As mentioned above, the Dynamic Type screen would not show anything when I tried to use UIFontPickerViewController to allow the user to choose a custom font. The app had the entitlement to choose fonts installed from other apps and works fine on iOS 13.

Image for post
Image for post
Adaptivity use installed font capability

After notarising the app I encountered a run time error when trying to execute it app.

As far as I can tell, it looks like Xcode is embedding an entitlements file with com.developer.user-fonts included into both the iOS and Catalyst builds. But the provisioning profile it automatically creates for Catalyst does not include it, creating the run-time mismatch. The iOS provisioning profile does have it.

I created a post on the developer forums and submitted it as Feedback 7599197. Neither have had any response. I encountered this issue in a beta version of Xcode 11.4. I need to try it again to see if it now works.

As I write this article, v6.0 of Adaptivity has been released today (Monday 30th March 2020). After a small propagation delay (despite scheduling an automatic release days in advance), the iOS App Store showed the new version reasonably quickly, in about an hour.

The Mac App Store, however, is still not showing the app in search results several hours later. Bizarrely, the app does appear in the “More by this developer” section at the bottom of the App Store entries for my other Mac Apps Pommie and XcLauncher. Tapping the app does show the app’s entry, but it is also showing a price instead of a download icon to users who have already purchased the iOS version. So much for universal purchases…

Despite this, some brave souls did click through and despite seeing this dialog, clicked “Buy”:

Image for post
Image for post
Image for post
Image for post
Mac App Store incorrectly warning users will be charged even though they have bought the iOS version and then saying “just kidding” 😭

I am hoping this is a temporary issue, related to whatever propagation delay is preventing the app from appearing in search results on the Mac App Store.

EDIT: the app did appear in search on the Mac App Store after a few more hours. I’m hearing mixed reports of whether users see a price or not. Perhaps people who bought the iOS version a long time ago are seeing the price whereas more recent purchasers will see the other platform show a download icon after a relatively short period of time.

Observations

I noticed a few differences in behaviour between the iOS and Catalyst versions of the app that are worth highlighting:

  • Base and elevated user interface levels in UITraitCollection do exist on Catalyst but have not affect on the resolved color for the dynamic colors
  • The resolved colors for some dynamic colors are different on Mac to iOS
  • The opaqueSeparator dynamic color resolves to the same color as separator in dark appearance on Mac: R:255 G:255 B:255 (10%). In light appearance on both platforms and in dark appearance on iOS, opaqueSeparator really is opaque.
  • Mac Catalyst never shows the preview view controller in context menus. That is, the UIContextMenuContentPreviewProvider passed to the initialiser for UIContextMenuConfiguration is ignored.
  • The context menu is displayed without images and destructive items are not shown in red. Items which lead to sub-menus do show a ►image.
  • Modally-presented view controllers appear in their final location without animation. They disappear with a fade out animation. There is no swipe-to-dismiss gesture since the presented view controller does not slide up or down when presented/dismissed.

Summary

On the whole I was very impressed how much functionality really did just work when I enabled Catalyst support. Even though I was deliberately avoiding making too many customisations to make this particular app more Mac-like, there was still quite a few changes required to make the app behave better and to work around some issues.

Other Articles That You Might Like

I have written comprehensive articles on How to Switch Your iOS App and Scene Delegates for Improved Testing and the View Controller Presentation Changes in iOS 13.

As an iOS developer you might be interested in my long-running series of articles which show how apps adapt to newer device sizes depending on the Xcode version they are built with:

You may not have realised that there were iPad Navigation Bar and Toolbar Height Changes in iOS 12.

I have also written about External Display Support on iOS, Working with Multiple Versions of Xcode and how to Hide Sensitive Information in the iOS App Switcher Snapshot Image.

If you found any of these articles helpful then please take a look at my apps in the iOS App Store to see if there’s anything that you’d like to download (especially the paid ones 😀).

If you work with a lot of Xcode projects you might like my Mac Menu Bar utility XcLauncher. It’s like having browser bookmarks for your favorite Xcode projects, workspaces and playgrounds. There is more information on my website about XcLauncher’s features.

Written by

Independent and freelance software developer for iPhone, iPad and Mac

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store