It seems in macOS 10.14 Mojave, the only way to create NSImage
instances that automatically draw a light and dark version is via asset catalogs and +[NSImage imageNamed:]
. However, I need to create dynamic images at runtime and there doesn't seem to be a way to do so without using private API.
Under the hood, it seems a private property _appearanceName
has been introduced to NSImageRep
that is somehow used to select the correct representation. It should be straight forward to create an NSImage
with image representations that have the corresponding _appearanceName
set but I would like to avoid this.
I found a simple workaround (posted below) but it doesn't seem to work correctly when the system appearance is changing (i.e. user is switching from light to dark or vice versa) or when used in view hierarchies that have the appearance
property set to different appearances (e.g. one view hard-coded to dark mode, another view hard-coded to light mode).
So, how can I manually create a dynamic NSImage
that is correctly showing a light or dark version, like the asset catalog images do?
@implementation NSImage (CustomDynamic)
+ (NSImage *)imageWithLight:(NSImage *)light dark:(NSImage *)dark
{
if (@available(macOS 10.14, *)) {
return [NSImage
imageWithSize:light.size
flipped:NO
drawingHandler:^(NSRect dstRect) {
if ([NSImage appearanceIsDarkMode:NSAppearance.currentAppearance]) {
[dark drawInRect:dstRect];
} else {
[light drawInRect:dstRect];
}
return YES;
}
];
} else {
return light;
}
}
+ (BOOL)appearanceIsDarkMode:(NSAppearance *)appearance
{
if (@available(macOS 10.14, *)) {
NSAppearanceName basicAppearance = [appearance bestMatchFromAppearancesWithNames:@[
NSAppearanceNameAqua,
NSAppearanceNameDarkAqua
]];
return [basicAppearance isEqualToString:NSAppearanceNameDarkAqua];
} else {
return NO;
}
}
@end
D'uh, it turned out the code posted in the question works just fine! The drawing handler is in fact called at appropriate times and does handle all the appearance situations.
However, I had code that scaled and cached those images and it was still using the ancient [image lockFocus]; … [image unlockFocus];
way of drawing images instead of using +[NSImage imageWithSize:flipped:drawingHandler:]
.