Undocumented NSShadow change on Catalina

December 18 2020 by Jeff Johnson

Apple has published AppKit release notes for macOS Big Sur 11 and for macOS 10.14 but strangely not for macOS 10.15 Catalina. Thus, it's difficult to determine what changed in AppKit for Catalina. I have discovered one thing that changed: the shadowOffset implementation of NSShadow. Curiously, the API documentation for shadowOffset is now shared between AppKit and UIKit.

These offsets are measured using the default user coordinate space and are not affected by custom transformations. This means that positive values always extend down and to the right from the user's perspective.

However, this contradicts the NSShadow header file in the macOS 10.15 (and 11.0) SDK!

@property NSSize shadowOffset; // offset in user space of the shadow from the original drawing, in default user space units, where positive values are up and to the right

I created a sample app to test this behavior:

@interface AppDelegate ()
@property (weak) IBOutlet NSWindow* window;
@property (weak) IBOutlet NSTextField* textField;
@end

@implementation AppDelegate
-(void)applicationDidFinishLaunching:(NSNotification*)notification {
	NSColor* color = [NSColor blackColor];
	NSShadow* shadow = [[NSShadow alloc] init];
	[shadow setShadowBlurRadius:2.0];
	[shadow setShadowColor:color];
	[shadow setShadowOffset:NSMakeSize(2.0, -2.0)];
	NSDictionary* attributes = @{NSForegroundColorAttributeName:color, NSShadowAttributeName:shadow};
	NSAttributedString* string = [[NSAttributedString alloc] initWithString:@"This is a shadow offset test." attributes:attributes];
	[[self textField] setAttributedStringValue:string];
}
@end

Below is how the shadow appears on macOS 10.14 Mojave and lower. This adheres to the comment in the header file, because positive values are up, but we used the negative value -2.0, so the shadow offset is down.

Shadow on macOS 10.14

And below is how the shadow appears on macOS 10.15 Catalina and later (including macOS 11 Big Sur). This adheres to the (new) online documentation rather than to the header file.

Shadow on macOS 10.15

So there is a change in behavior of NSShadow shadowOffset in AppKit on Catalina, presumably to emulate UIKit. Sigh… why does the Mac always have to emulate iOS now? Unfortunately, AppKit developers were not informed of the change, because there were no AppKit release notes published for Catalina.

If you want the shadowOffset to behave as (you) intended on all versions of macOS, both before and after Catalina, the workaround is to check if (@available(macOS 10.15, *)) and switch the sign of your offset height accordingly.

Addendum, December 22 2020

Jonathan Deutsch and Maxim Ananov have informed me that the shadowOffset behavior did not change on Catalina for -[NSShadow set], which can be used for example in -[NSView drawRect:]. I've now tested this myself and can confirm their results: -[NSShadow set] behaves the same on High Sierra through Big Sur. Thus, the shadowOffset change on Catalina and later only occurs when NSShadow is used as an NSAttributedString attribute. Bizarrely, the exact same NSShadow instance appears different when used with -[NSShadow set] and NSAttributedString on Catalina and Big Sur. The shadow's offset goes in opposite directions.

This could, arguably should be considered a bug. However, the "bug" has persisted for 1.5 years through 2 major macOS versions, and the API documentations is still ambiguous about the expected behavior.

Jeff Johnson (My apps, PayPal.Me)