切换暗模式时 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()
}
我有一个仅在 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()
}