切换暗模式时 NSAppearance 未更新

NSAppearance is not updating when toggling dark mode

我有一个仅在 macOS 状态栏中运行的 macOS 应用程序。我将 Info.plist 中的“Application is agent (UIElement)”属性 更改为“YES”:

<key>LSUIElement</key>
<true/>

我有一个计时器,每 5 秒打印一次外观的名称,如下所示:

Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
    let appearance = NSAppearance.currentDrawing()
    print(appearance.name)
}

问题

当我在系统设置中切换 dark/light 模式时,名称实际上并没有改变。它始终打印应用程序启动时设置的外观名称。

有没有办法监听系统外观变化?

目标

我的最终目标实际上是将 NSAttributedString 绘制到 NSImage,并将 NSImage 用作 NSStatusItem 按钮的图像。

let image: NSImage = // generate image
statusItem.button?.image = image

对于属性字符串中的文本,我使用 UIColor.labelColor 应该基于系统外观。不过似乎不尊重系统外观的变化。

当我在深色模式下启动应用程序然后切换到浅色模式时:

当我以浅色模式启动应用程序然后切换到深色模式时:

旁注

为什么我把NSAttributedString变成NSImage而不直接在NSStatusItem按钮的[=]上使用NSAttributedString 22=] 是因为 it doesn't position correctly in the status bar.

绘制 NSAttributedString 的问题在于,NSAttributedString 不知道如何呈现 NSColor.labelColor 等动态颜色。因此,它不会对外观变化做出反应。您必须使用 UI 元素。

解决方案

我通过将 NSAttributedString 传递给 NSTextField 并将其绘制到 NSImage 中解决了这个问题。工作得很好。

func updateStatusItemImage() {

    // Use UI element: `NSTextField`
    let attributedString: NSAttributedString = ...
    let textField = NSTextField(labelWithAttributedString: attributedString)
    textField.sizeToFit()

    // Draw the `NSTextField` into an `NSImage`
    let size = textField.frame.size
    let image = NSImage(size: size)
    image.lockFocus()
    textField.draw(textField.bounds)
    image.unlockFocus()

    // Assign the drawn image to the button of the `NSStatusItem`
    statusItem.button?.image = image
}

对 NSAppearance 变化作出反应

此外,由于 NSImage 也不知道 NSAppearance 我需要通过观察按钮的 effectiveAppearance 属性 来触发外观变化的重绘NSStatusItem:

observation = statusItem.observe(\.button?.effectiveAppearance, options: []) { [weak self] _, _ in
    // Redraw 
    self?.updateStatusItemImage()
}