Some BS AppKit notes

November 27 2020 by Jeff Johnson

This blog post describes a few things I found while "adapting" my AppKit apps for macOS 11 Big Sur. Apple has documented some changes in its AppKit Release Notes for macOS Big Sur 11, but these are not comprehensive. My blog post is not comprehensive either, but it does go beyond Apple's release notes. I should also mention the helpful site macOS Big Sur changes for developers, which also goes beyond Apple's release notes.

macOS 11 Runtime

This section of the blog post discusses changes that affect every AppKit app running on macOS 11, regardless of which SDK was used to compile the app. The next section will discuss changes that occur when you compile your AppKit app with the macOS 11 SDK.

The most obvious change is that macOS 11 is much brighter. The user experience is like… let me think of an analogy… staring directly at the sun. If Mojave was about making everything dark, then Big Sur is about making everything light. I guess that white is the new black. Even the grays are whiter. Prior to Big Sur, +[NSColor gridColor] returned +[NSColor _gray204Color], which both surprisingly and unsurprisingly has the RGB values 204. On Big Sur, in contrast, +[NSColor gridColor] seems to be around RGB 231. This messed me up a little bit, because +[NSColor alternateSelectedControlTextColor] (basically, white) is harder to read against an RGB 231 background than against an RGB 204 background. Admittedly, +[NSColor gridColor] isn't intended as the background for text, but I liked it because it automatically adjusted for dark mode, and it had always looked fine on Catalina and earlier.

Another thing that messed me up is that macOS 11 slightly changed the alignmentRectInsets for NSButtonTypeSwitch. The edge insets prior to Big Sur were (top = 2, left = 2, bottom = 2, right = 2), but on Big Sur they're (top = 1, left = 2, bottom = 1, right = 0). This change may seem insignificant, but if you have 12 checkboxes vertically stacked, that's a 24 point difference! To make the checkboxes fit correctly again inside my view, I had to reduce the vertical space between the checkboxes by 2 on Big Sur.

macOS 11 SDK

When you compile your app with the macOS 11 SDK, it can change your app's behavior on macOS 11, because AppKit on macOS 11 checks whether your app was compiled with that SDK. I immediately noticed that NSImageNameLockLockedTemplate and NSImageNameLockUnlockedTemplate were different images from what they were when I compiled with the macOS 10.15 SDK. This change didn't cause me any problems, but it's… strange.

The changes that did cause me problems were to NSTableView. The macOS 11 SDK changed the default intercellSpacing and rowHeight of NSTableView, as well as adding a new style property. Prior to Big Sur, the default intercellSpacing was (width = 3, height = 2), but on Big Sur it's (width = 17, height = 0). Big Sur really loves its whitespace!

According to Apple's API documentation, "The default row height is 16.0." But this is wrong. It's been wrong for a long time. According to the comments in the NSTableView.h header file from the macOS 10.15 SDK, "The default value is 17.0 for applications linked on 10.5 and higher (the height acceptable for [NSFont systemFontSize]). The default value is 16.0 for 10.4 and lower." Mac OS X 10.5 Leopard was released in 2007, so… yeah. (Have I told you about the time I filed a Radar about an inaccurate man page, and it was fixed 9 years later?) Anyway, the macOS 11 SDK changed the default rowHeight to 24.

I was only compiling with the macOS 11 SDK because it comes with Xcode 12, which you need to build universal binaries for Apple silicon. (Or is it Apple Silicon? Si) I didn't want to redesign my user interface (or my app icons!) for Big Sur. Thus, my goal was simply to make the apps look like they did on Catalina and earlier. It's easy enough to hard-code the values so that they're the same on every version of macOS you support:

[tableView setIntercellSpacing:NSMakeSize(3.0, 2.0)];
[tableView setRowHeight:17.0];

However, the macOS 11 SDK still applies some new, unwanted (by me) insets to NSTableView. I found the solution (eventually, after trial and error) in the header file:

// A plain style. No insets, padding or any other kind of decoration applied to the row or its background. The cells are equally spaced in the row using intercellSpacing.width.

Once again, Apple's API documentation was pretty useless: "No overview available."

The catch to using NSTableViewStylePlain is that it's only defined in the macOS 11 SDK, and indeed the entire API only exists on macOS 11.

// The table view style. Defaults to NSTableViewStyleAutomatic
@property NSTableViewStyle style API_AVAILABLE(macos(11.0));

Moreover, I still want to be able to build and debug my apps with Xcode 11 on Mojave. So I had to use a little trick. First, declare the macOS 11 method in a category:

@interface NSTableView(BigSurHack)

Then replace the enumeration constant with its literal value:

if (@available(macOS 11.0, *)) {
    [tableView setStyle:4]; // NSTableViewStylePlain

That's the Objective-C implementation. In Swift, an alternative implementation is required, using Key-Value Coding (KVC):

if #available(macOS 11.0, *) {
    tableView.setValue(4, forKey:"style") // NSTableViewStylePlain

One more thing. Everything above that applies to NSTableView also applies to its subclass NSOutlineView. You need to use the same hacks if you want your outline view to look "right" again on Big Sur. After all, the HIG is effectively (defectively) defunct now, so you can use your own judgment. But for goodness sake, please don't use a rounded rect.

Jeff Johnson (My apps, PayPal.Me)