NSStatusItem更改图像以适应暗色调

26

在OSX 10.10测试版3中,苹果发布了他们的深色调选项。不幸的是,这也意味着几乎所有状态栏图标(除了我见过的苹果和Path Finder),包括我的图标,在暗色背景下仍然保持黑色。我该如何为应用深色调时提供替代图像?

我没有看到NSStatusBarNSStatusItem上的API更改,我假设它是一个通知或者某个响应式的东西,可以轻松地在用户改变调色板时进行更改。

绘制图像的当前代码被封装在NSView中:

- (void)drawRect:(NSRect)dirtyRect
{
    // set view background color
    if (self.isActive) {
        [[NSColor selectedMenuItemColor] setFill];
    } else {
        [[NSColor clearColor] setFill];
    }

    NSRectFill(dirtyRect);

    // set image
    NSImage *image = (self.isActive ? self.alternateImage : self.image);
    _imageView.image = image;
}

你是在自定义视图的 -drawRect: 方法中手动绘制图像吗?使用 NSImageView 子视图会有帮助吗? - Ken Thomases
@KenThomases 应该添加当前代码。现在已经添加了。 - Joel Fischer
1
你应该使用-drawStatusBarBackgroundInRect:withHighlight:而不是自己选择填充颜色。如果这样做没有帮助,你可以尝试在图像视图上设置背景样式。当然,这就引出了如何选择适当的样式的问题。另外,我会将图像视图的图像设置移动到-viewWillDraw中。这种事情不应该在-drawRect:中完成。 - Ken Thomases
谢谢您的输入,但我认为您误解了。背景变暗是合适的,问题在于前景图像仍然是一张黑色图像放在现在的黑色背景上。我需要知道何时将其替换为白色前景图像。 - Joel Fischer
我的观点是,NSImageView 很可能能够区分出使用你选择的颜色填充的背景和使用我提到的适当方法绘制的背景之间的差异。它可能会相应地改变其行为。另外,你是否在使用模板图像? - Ken Thomases
显示剩余2条评论
6个回答

64

简而言之:在深色主题下,您不需要进行任何特殊操作。给NSStatusItem(或NSStatusBarButton)一个模板图像,它将在任何菜单栏上下文中正确地应用样式。


一些应用程序(如PathFinder)的状态项目已经在暗色主题下正常工作的原因是它们没有在StatusItem上设置自定义视图,而只是在StatusItem上设置了一个模板图像。

类似这样的东西:

_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSImage *image = [NSImage imageNamed:@"statusItemIcon"];
[image setTemplate:YES];
[_statusItem setImage:image];

在 Mavericks 及之前版本中,以及 Yosemite 和任何未来的版本中,这个功能都能很好地发挥作用,因为它允许 AppKit 根据状态项的状态对图像进行所有的样式设置。

Mavericks

在 Mavericks(和早期版本)中,只有两种独特的状态。未按下和已按下。这两种状态看起来几乎完全是黑色和白色,分别是“纯黑色”并不完全正确 - 还有一种小的效果使它们看起来稍微嵌入。

由于只有两种可能的状态,状态栏应用程序可以设置自己的视图,并通过只绘制黑色或白色来轻松获得相同的外观,具体取决于它们的突出状态。(但请注意,它不是完全黑色,因此应用程序必须在图像中构建效果,或者满足于一个不太显眼的不适当图标)。

Yosemite

在 Yosemite 中,状态项至少有 32 种独特的样式。 在暗主题下未按下仅是其中之一。 应用程序没有实际(或不切实际)的方法来对项目进行自定义样式,并在所有情况下看起来正确。

以下是其中六个可能的样式示例:

六种可能的状态项样式

非活动菜单栏上的状态项现在具有特定的样式,与过去简单的不透明度变化不同。禁用外观是另一种可能的变化; 还有其他附加的 维度 来增加这个样式矩阵。

API

将任意视图设置为 NSStatusItem 的 view 属性无法捕获所有这些变化,因此它(以及其他相关 API)在 10.10 中被弃用。

然而,种子 3 引入了新的 NSStatusItem API:

@property (readonly, strong) NSStatusBarButton *button NS_AVAILABLE_MAC(10_10);

这个API有以下几个作用:

  1. 应用程序现在可以在不设置自定义视图的情况下获取状态栏项目的屏幕位置(或从中显示弹出窗口)。
  2. 消除了对于NSStatusItem上imagetitlesendActionOn:等API的需求。
  3. 提供了一个新API的类:即looksDisabled。这使得应用程序可以获得标准的禁用/关闭样式(例如当蓝牙/Time Machine关闭时)而无需使用自定义图像。

如果当前(非自定义视图)API无法实现所需功能,请提出增强请求。StatusItems应该以一种适用于所有状态栏项目的行为或外观方式提供。


更多讨论请参见 https://devforums.apple.com/thread/234839,但是我已经在这里总结了大部分内容。


2
注意:在种子3中,使用[self.statusItem.button sendActionOn:(NSLeftMouseUpMask|NSRightMouseUpMask)];存在问题,右键单击事件无法触发。这里有一个开放的雷达:http://openradar.appspot.com/radar?id=5882037839855616 - Joel Fischer
4
你还可以在图像文件名后面加上“Template”后缀,比如“MyIconTemplate.png”,这样它就可以直接使用了。值得一提的是,这种方法的美妙之处在于,我曾经能够通过将黑色图标文件的最后8个字符替换为“Template”,并使用十六进制编辑器编辑编译构建中的文件引用来破解一个已编译应用程序。现在它非常完美! :) - Bruno Philipe
目前,NSStatusItem上的“image”和“setImage”也已在10.10中被弃用。 - mirosval
1
是的。对于10.10及更高版本,您只需在StatusItem的按钮上设置图像(在10.10上可用)。 - Taylor
这里有一个小型库,根据 OS X 版本处理菜单栏。可以用作示例:https://github.com/dmitrynikolaev/MenuBarController - dmitrynikolaev

6

以下是我在自定义拖放 NSStatusItemView 中所做的操作(使用 Swift):

var isDark = false

func isDarkMode() {
    isDark = NSAppearance.currentAppearance().name.hasPrefix("NSAppearanceNameVibrantDark")
}

override func drawRect(dirtyRect: NSRect) {
    super.drawRect(dirtyRect)
    isDarkMode()
    // Now use "isDark" to determine the drawing colour.
    if isDark {
        // ...
    } else {
        // ...
    }
}

当用户在系统偏好设置中更改主题时,系统会调用NSView进行重新绘制,您可以相应地更改图标颜色。
如果您希望调整视图之外的其他自定义UI,则可以使用KVO来观察视图的isDark键,或者自己处理。

拖放是 NSStatusItem 中唯一需要使用自定义视图的缺失行为吗?您是否已经提交了支持该行为的反馈报告? - Taylor
目前是@Taylor,是的。不,我没有请求它。 - Cai

2

最新的Swift代码设置图像模板方法在这里:

// Insert code here to initialize your application
if let button = statusItem.button {
    button.image = NSImage(named: "StatusIcon")
    button.image?.isTemplate = true  // Just add this line
    button.action = #selector(togglePopover(_:))
}

当暗模式启动时,它将更改图像。


2

我创建了一个基本的NSStatusItem包装器,您可以使用它来提供对10.10及更早版本自定义状态栏视图的支持。您可以在此处找到它:https://github.com/noahsmartin/YosemiteMenuBar 基本想法是将自定义视图绘制成NSImage,并将此图像用作状态栏项目的模板图像。该包装器还将单击事件转发到自定义视图,以便可以像之前的10.10版本一样处理它们。该项目包含如何在状态栏上使用自定义视图的YosemiteMenuBar基本示例。


看起来很有用,你如何处理像加载动画这样的动态元素? - Joel Fischer

1

但是,如果您确实想要监视状态更改,您可以这样做。我也知道有一种更好的方法来确定轻/暗模式,但我现在记不起来了。

// Monitor menu/dock theme changes...
[[NSDistributedNotificationCenter defaultCenter] addObserver: self selector: @selector(themeChange:) name:@"AppleInterfaceThemeChangedNotification" object: NULL];

//
-(void) themeChange :(NSNotification *) notification
{
    NSLog (@"%@", notification);
}

1
当您的应用程序绘制任何GUI元素时,您可以通过[NSAppearance currentAppearance]获取其外观,它本身具有一个name属性,保存类似于以下内容的东西。
NSAppearanceNameVibrantDark->NSAppearanceNameAqua->NSAppearanceNameAquaMavericks

第一部分是外观的名称,也可以作为常量在NSAppearanceNameVibrantDarkNSAppearanceNameVibrantLight中使用。
我不知道是否有方法只获取第一部分,但我认为现在这个代码可以解决问题。
示例代码:
-(void)awakeFromNib {
    NSStatusItem* myStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
    myStatusItem.title = @"Hello World";

    if ([[[NSAppearance currentAppearance] name] containsString:NSAppearanceNameVibrantDark]) {
        myStatusItem.title = @"Dark Interface";
    } else {
        myStatusItem.title = @"Light Interface";
    }
}

感谢您的输入。对于基于更改的修改,有什么想法吗?是否有通知?我可能可以使用KVO,但我认为还有比这更好的方法,因为Path Finder在beta 3发布后立即起作用了。 - Joel Fischer
如果状态项正在运行,那么它不会自动调整主题更改。 - Taylor
@JoelFischer,你试过KVO方法了吗?我尝试了一些类似于NSAppearance.currentAppearance().addObserver(self, forKeyPath: "name", options: NSKeyValueObservingOptions.Prior, context: nil)的东西。它可以检测到更改,但会导致错误An instance 0x608000465cc0 of class NSCompositeAppearance was deallocated while key value observers were still registered with it.。不知道怎么解决。 - Cai
@x43x61x69 我没有测试过,但据我所知,不仅名称会改变,整个(当前)外观对象(带有您的 KVO)也会被替换。因此,您最好观察 currentAppearance 而不仅仅是它的名称。 - max

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接