Switch app between Dark and Light mode with NSAppearanceMBS

How do I switch my whole app from Dark to Light Mode and vice versa with NSAppearanceMBS? The code of the NSAppearanceMBS:

dim t as string = NSAppearanceMBS.NSAppearanceNameVibrantDark

dim a as NSAppearanceMBS = NSAppearanceMBS.appearanceNamed(t)

if a = nil then
  MsgBox "No Appearance with that name?"
else
  NSAppearanceMBS.setCurrentAppearance a
  NSAppearanceMBS.setAppearance(self, a) // apply to window
end if

produces very odd results in complex windows. And only one window is switched:

The main window in the background of the screenshot doesn’t switch modes at all. The window in the front of the screenshot uses custom colors so that the grey background is okay. But why do the listbox and the checkbox look okay while all the labels are not okay?

Switching via OS methods may not trigger Xojo framework to switch with it.

So what you say is “tough luck”?

Declare Function NSClassFromString Lib "AppKit" (inClassName As CFStringRef) As Integer
Declare Sub setAppearance Lib "AppKit" Selector "setAppearance:" (NSApplicationInstance As Integer, NSAppearanceInstance As Integer)
Declare Function sharedApplication Lib "AppKit" Selector "sharedApplication" (classRef As Integer) As Integer
Declare Function NSAppearanceNamed Lib "AppKit" Selector "appearanceNamed:" (NSAppearanceClass As Integer, name As CFStringRef) As Integer

setAppearance(sharedApplication(NSClassFromString("NSApplication")), _
  NSAppearanceNamed(NSClassFromString("NSAppearance"), _
  "NSAppearanceNameVibrantLight"))

Thank, that works at least on all windows.

1 Like

But that sets appearance for whole app.
While your code above tried to set it for a window!?

What do I need to change to affect all windows?

Something like

NSAppearanceMBS.setAppearance(app, a)

Great.

“Das funzt.”.

Both of these will produce weird results, as technically we’re not supposed to directly use these values, instead these are meant to read during the paint or updateLayer event (same for the High Contrast versions).

The ones we should use are

NSAppearanceNameAqua

and

NSAppearanceNameDarkAqua

However DarkAqua doesn’t exist on 10.13, so then you have to use “NSAppearanceNameVibrantDark” if you want any resemblance of a system dark theme.

Good to know. The declare made my app crash anyways on High Sierra.

It was a hard coded sample. You can find all possible names at NSAppearanceName | Apple Developer Documentation including macOS version hints.

Next question @Christian_Schmitz : how do I get the system mode for Dark or Light? Color.IsDarkMode gives me the current appearance.

Well, NSAppearanceMBS class has a name property and when the name contains “dark”, then it’s a dark mode.

So if NSAppearanceMBS.currentAppearance contains dark or is same text as in NSAppearanceNameDarkAqua, then you have dark.
Or compare to NSAppearanceMBS.NSAppearanceNameVibrantDark for older systems.

Well, as I thought the NSAppearanceMBS.currentAppearance is for the current appearance of my app. I need the global appearance setting of macOS.

Hi Beatrix,

If you need the light or dark mode of macOS in general, call NSAppearanceMBS.currentAppearance in your App.Opening Event and store the result in a property of the App Class, for later use.

Do the above assignment, before having your app logic switch it’s light or dark mode appearance.

Works for me. This allows me to provide three appearance options in an app, that can be changed at any time:

  • System Assigned
  • Light
  • Dark

Note: I am also using the MBS plugins to manage appearance changes.

1 Like

Yeah it will do. I’ll let @Martin_T explain why.

currentAppearance was deprecated for macOS 11 / 10.16, like Xojo, Apple renamed the function name to currentDrawingAppearance from macOS 11 onwards.

Please note that this function (and currentAppearance) are only guaranteed to return the correct appearance from certain events.

  • Paint
  • updateLayer (which Xojo doesn’t expose)
  • viewDidChangeEffectiveAppearance (which Xojo doesn’t expose)

But also in those events you can use effectiveAppearance on the control to determine appearance. Appearances can alter based upon their parent controls.

You can revert to the system default by setting the appearance of a control or the application object to nil and then it should adopt the current system appearance. Once you’ve set it to nil, you can then retrieve it and match it’s name against the constants.

How do I reset the appearance of the app?

I assume with the MBS, you can simply pass NIL as the appearance object.

With the declares you pass in zero 0 for the appearance object.