Handling dark mode changes when viewDidChangeEffectiveAppearance is not available

I'm updating an app for dark mode, and I have one view in particular that's more complicated than most. Here are the important details:


  • There's a sidebar on the left side with a table view. Because of the content in the table view, it uses a "dark vibrant" appearance. This view will not change based on the current appearance.
  • The window has a toolbar along the top, including a search bar.
  • When the search bar is selected, a segmented control appears at the top of the sidebar (to change the scope of the search). This control and its background do need to change their apperance to match the window/toolbar or it won't look correct.


I'm using some code like this to update the appearance of the scope bar:


  private func updateAppearance(forWindow window: NSWindow?) {
       if #available(OSX 10.14, *) {
            let newAppearance = window?.effectiveAppearance
            material = .titlebar     
            appearance = newAppearance
            needsDisplay = true
       }
  }


This works fine, but the problem is this method has to be called manually any time the apperance changes. Since my sidebar always has a dark appearance, I can't use viewDidChangeEffectiveAppearance anywhere inside of it.


I'm trying to figure out the best way to handle this, and I can't think of anything that isn't really messy. Since only *views* get notified of appearance changes, and that whole view hierarchy is out of the question, it seems like I'm basically forced to subclass some unrelated view somewhere else so it can send a notification that the appearance has changed. It'd be nice if I could at least have my window controller take on that responsibility, but I don't see any way to do that.


Any suggestions?

Accepted Reply

I got a suggestion elsewhere that I could use KVO to observe the effectiveAppearance property. That made for an easy fix, adding this code to my scope bar view:


  private var appearanceObserver: NSKeyValueObservation?

  override func viewWillMove(toWindow newWindow: NSWindow?) {
       super.viewWillMove(toWindow: newWindow)
       if let window = newWindow {
            appearanceObserver = window.observe(\.effectiveAppearance) { [weak self] (window, change) in
                 self?.updateAppearance(forWindow: window)
            }
       } else {
            appearanceObserver = nil
       }
  }


Works perfectly.

Replies

I got a suggestion elsewhere that I could use KVO to observe the effectiveAppearance property. That made for an easy fix, adding this code to my scope bar view:


  private var appearanceObserver: NSKeyValueObservation?

  override func viewWillMove(toWindow newWindow: NSWindow?) {
       super.viewWillMove(toWindow: newWindow)
       if let window = newWindow {
            appearanceObserver = window.observe(\.effectiveAppearance) { [weak self] (window, change) in
                 self?.updateAppearance(forWindow: window)
            }
       } else {
            appearanceObserver = nil
       }
  }


Works perfectly.