I have a macOS app that runs only in the macOS status bar. I changed the "Application is agent (UIElement)" property in the Info.plist to "YES":
<key>LSUIElement</key>
<true/>
I have a timer that prints out the appearance's name every 5 seconds like this:
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
let appearance = NSAppearance.currentDrawing()
print(appearance.name)
}
Problem
The name doesn't actually change when I toggle dark/light mode in system settings. It always prints the name of the appearance that was set when the application launched.
Is there a way to listen to system appearance changes?
Goal
My end goal is actually to draw an NSAttributedString
to an NSImage
, and use that NSImage
as the NSStatusItem
button's image.
let image: NSImage = // generate image
statusItem.button?.image = image
For the text in the attributed string I use UIColor.labelColor
that is supposed to be based on the system appearance. However it seems to not respect the system appearance change.
When I start the application in Dark Mode and then switch to Light Mode:
When I start the application in Light Mode and then switch to Dark Mode:
Side note
The reason why I turn the NSAttributedString
into an NSImage
and don't use the NSAttributedString
directly on the NSStatusItem
button's attributedTitle
is because it doesn't position correctly in the status bar.
The problem with drawing a
NSAttributedString
is, thatNSAttributedString
doesn't know how to render dynamic colors such asNSColor.labelColor
. Thus, it doesn't react on appearance changes. You have to use a UI element.Solution
I solved this problem by passing the
NSAttributedString
to aNSTextField
and draw that into anNSImage
. Works perfectly fine.React on NSAppearance changes
In addition, since
NSImage
doesn't know aboutNSAppearance
either I need to trigger a redraw on appearance changes by observing theeffectiveAppearance
property of the button of theNSStatusItem
: