无法匹配具有自定义视图的Yosemite NSMenuItem的生动背景。

26

我正在尝试将自定义视图添加到OS X 10.10 Yosemite菜单栏中的NSMenuItem。

自定义视图只是一个带有NSTextField“标签”的NSView背景。

问题在于,当将其添加到菜单中时,背景NSView会获得Yosemite风格的模糊/透明效果,而NSTextfield标签则不会。

Background colors do not match

通过使用NSRectFillUsingOperation,我已经使某些背景颜色在Yosemite中看起来很好。但其他颜色继续不匹配。当它有效时,在手动“突出显示”视图后,原始颜色会发生变化,不再匹配。如果需要,我可以找到一些示例代码。

然后,当在Yosemite中看起来还不错时,在10.9 Mavericks中看起来非常糟糕。

我还尝试将wantsLayer属性设置为YES,将该视图转换为CALayer支持的视图。这会创建其他问题,例如文本未正确针对清晰背景进行抗锯齿处理。

我的问题:

如何在NSMenuItem自定义视图的顶部显示标签? 标签的背景必须完全匹配视图的背景。 解决方案必须在Yosemite和Mavericks中均可行。

以下是示例代码:

self.statusItem = [[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength];
[self.statusItem setTitle:@"TEST"];
[self.statusItem setHighlightMode:YES];
[self.statusItem setEnabled:YES];
[self.statusItem setTarget:self];

NSMenu *menu = [[NSMenu alloc] init];
[menu addItemWithTitle:@"Disabled menu item" action:nil keyEquivalent:@""];
[menu addItemWithTitle:@"Enabled menu item" action:@selector(enabled) keyEquivalent:@""];

NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(30, 20, 50, 20)];
label.stringValue = @"label";
label.editable = NO;
label.bordered = NO;
label.backgroundColor = [NSColor blueColor];
//label.backgroundColor = [NSColor clearColor];


PKMenuItemView *view = [[PKMenuItemView alloc] initWithFrame:NSMakeRect(0, 0, 200, 50)];
[view addSubview:label];

NSMenuItem *viewMenuItem = [[NSMenuItem alloc] init];
[viewMenuItem setView:view];
[menu addItem:viewMenuItem];

self.statusItem.menu = menu;

我已经创建了一个NSView的子类来重写drawRect:方法并绘制一个有颜色的背景:


- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];

    [[NSColor blueColor] setFill];
    NSRectFill(dirtyRect);
    //NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
}

1
你能把标签的背景设为透明吗? - A O
这并不像简单地为标签分配clearColor背景那么简单。这会导致一些问题的组合:模糊的标签文本,一个实际上并非透明的背景,或者是完全透明的背景,在Mavericks中会显示到桌面上。 - pkamb
1
啊,对不起,我提了一个显而易见的建议。这是我在没有进行深入研究的情况下能做出的最好贡献了。 - A O
在这里查看_viewHandlesEvents的答案:https://dev59.com/4VPTa4cB1Zd3GeqPnuxg - pkamb
FB6143600 - 为具有自定义视图的NSMenuItem启用简单的VEV背景 - pkamb
7个回答

8

这确实有点“hack”,但对我很有效。尝试向您的自定义视图添加一个带有空图像的NSImageView。 图片视图必须占据整个视图。

使用NSImageView


有趣,我会试一下。你的应用程序也遇到了同样的问题吗? - pkamb
我在Yosemite上的应用程序中,使用NSMenu和自定义菜单项视图时遇到了非常相似的问题。我正在使用支持图层的自定义视图,并设置self.layer.backgroundColor = [[CGColor clearColor] CGColor]; - Matthes
不小心没完成它...我的视图包含小图像视图和普通的NSTextFiled。我想在鼠标悬停时突出显示菜单项,所以我添加了跟踪区域,并在mouseDown上设置self.layer.backgroundColor = self.highlightBGColor。菜单被突出显示,但是图像都带有白色背景,尽管它们都是透明的。当背景颜色重新设置为清除颜色时,图像再次正常绘制。不知道如何修复它。 - Matthes
1
这个有用,谢谢!我还想澄清一下,“空白图像”表示您不需要设置图像。只需将NSImageView设置为第一个子视图并将其设置为填充父视图即可。 - BonzaiThePenguin
2
虽然这比不使用NSImageView看起来好得多,但它与其他菜单项不太相同,因为它没有任何半透明效果。如果有人知道如何匹配这种风格,那将是很好的。 - yesthisisjoe

5
我认为我有一个较少“hackish”的解决方案。它确实是由Yosemite中新的NSVisualEffectView和Vibrancy引起的。我了解到当视图是NSVisualEffectView的子视图时,绘制视图时有相当复杂的规则。这在WWDC 2014的220会议中讨论过-采用OS X Yosemite的新UI的高级功能。 我建议您观看此会议视频以获得全面的解释。
简而言之,似乎您的问题可能是由您使用的颜色引起的。有两种新的系统颜色- [NSColor labelColor][NSColor secondaryLabelColor]。当在NSVisualEffectView内部绘制这两个颜色时,它们会自动调整。此外,您的自定义视图应支持Vibrancy效果。这可以通过覆盖- (BOOL)allowsVibrancy方法并返回YES来完成。
请查看上面提到的会议视频或下载PDF会议幻灯片以获取精确信息。 这些内容从PDF的124页开始讨论,并在视频中间附近讨论。

5

很不幸,Yosemite目前存在几个问题。正如Matthes所提到的,您可以使用labelColor()secondaryLabelColor()。使用这些颜色不会导致您看到奇怪的背景。

但是,labelColor()只适用于VibrantDark,因为在此情况下,当NSMenuItem被同时突出显示和未突出显示时,标签颜色为白色。对于VibrantLight,labelColor是黑色的,因此在蓝色高亮显示的上方非常难以阅读。

至于NSMenuItem自定义视图的高亮颜色,人们可能认为应该使用selectedMenuItemColor(),因为其名称。然而,问题在于,该颜色实际上与没有自定义视图的NSMenuItems中看到的菜单高亮颜色并不匹配。这个颜色对于VibrantLight和VibrantDark来说都完全错误。

简述:如何创建一个自定义的NSMenuItem,它使用完全相同的文本颜色和高亮颜色?你不能。你应该使用和,但前者只对VibrantDark起作用,而后者根本不匹配。
我真的希望我错了,因为我也在尝试实现同样的事情:(
编辑:这里是一个示例项目,如果有人想看看。

谢谢。是的,我相信我尝试过labelColorselectedMenuItemColor,但在浅色/深色模式下仍然存在同样的问题。这是非常烦人的问题,因为我认为我已经解决了它,但是一旦切换到我的Mavericks电脑或浅色/深色模式,它又变得不对了。 - pkamb
你尝试过在菜单项被选中时为文本字段的单元格设置backgroundStyle,以查看是否会导致标签颜色反转吗? - Ken Thomases
我刚刚尝试了所有的backgroundStyle值,但都没有起到任何帮助作用。 - iMaddin
@iMaddin 感谢提供示例项目。像你所说的那样,不幸的是所有选项都存在某种问题... - pkamb

4

我在2015年提交了一张苹果开发技术支持工单,以下是回复:

关于DTS自动确认 - 自定义视图NSMenuItem的鲜艳背景和高亮

这是一个难以解决的问题,特别是考虑到菜单选择绘制并不适用于具有自定义视图的菜单项,并且菜单选择绘制(颜色等)可能会在未来发生变化。这就是为什么我们要求您提交错误报告,以便如果需要,菜单选择将受到自定义视图的尊重,以便未来对OS X的更改不需要开发人员不断维护其代码以匹配未来的颜色外观。

“应用程序菜单和弹出列表编程主题”中提到:

菜单项中的视图 -

“带有视图的菜单项不会绘制其标题、状态、字体或其他标准绘制属性,并完全将绘制责任分配给视图。键盘快捷键和类型选择仍然使用键等效项和标题作为正常。”

由于所有绘制都由开发人员完成,因此菜单项中的自定义视图不一定应该绘制“选定”状态。

获取正确选择颜色的API显然没有执行其预期功能,因此要求提交错误报告。我们希望能够提供更具体的解决方案,但是今天提供的解决方法可能明天就不再适用,并且我们不想为风险较高的解决方法设立不良先例。苹果应用程序可以访问更低级别的私有API来实现它们的结果。由于这些API是私有的,我们无法向您提供这些解决方案。

如果selectedMenuItemColor()与Vibrant light和dark的菜单高亮颜色不匹配,则需要提交错误报告并进行修复。

最后,苹果建议尽可能使用NSMenuItem的API在菜单中实现所需功能。您包含的截图很可能可以在不应用自定义视图的情况下完成。


3
我刚刚发现在El Capitan中,+[NSColor keyboardFocusIndicatorColor]是正确的颜色,而预期的selectedMenuItemColor要暗得多。

1
经过进一步使用,我意识到它的行为并不完全像本机菜单,但它很接近... - Micha Mazaheri
1
很不幸,这在夜间模式下表现不佳。 - yesthisisjoe
这是完全可能的,@yesthisisjoe!你找到更好的解决方案了吗?我自己还在非常努力地寻找!如果你有什么想法,将不胜感激! - Micha Mazaheri
1
我最终使用了一个属性字符串而不是自定义视图。所以,不算真正的自定义视图。 - yesthisisjoe
我最终使用了一个属性字符串而不是自定义视图。所以,不算真正的自定义视图。 - yesthisisjoe

2

根据WWDC的AppKit工程师,这种方法并不适用于NSMenuItem。我也将这个答案添加到了这个问题中。

他们建议使用NSPopover来创建一个伪造的NSMenu,附加到NSStatusItem菜单栏助手上。

使用类似下面的代码会产生充满活力的背景选择:

最初的回答:

override func viewDidLoad() {
    super.viewDidLoad()

    let visualEffectView = NSVisualEffectView()

    visualEffectView.material = .selection
    // .menu or .popover for the non-selected background.

    visualEffectView.state = .active
    visualEffectView.blendingMode = .behindWindow
    visualEffectView.isEmphasized = true

    let label = NSTextField(labelWithString: "Hello, world!")
    label.cell?.backgroundStyle = .emphasized
    visualEffectView.addSubview(label)
    visualEffectView.frame = view.bounds
    label.setFrameOrigin(.zero)
    view.addSubview(visualEffectView)
}

Vibrant Window Background


0
在2019年WWDC的AppKit实验室中,我与来自AppKit团队的工程师一起解决了这个问题。
他们很惊讶默认情况下它不起作用,并鼓励我提交(更多)radars:

FB6143574 - 公开NSMenuItem的私有API“_viewHandlesEvents”

他们知道NSMenuItem上有一个私有API“_viewHandlesEvents”。
// VibrantMenuBar-Bridging-Header.h

#import <AppKit/AppKit.h>

@interface NSMenuItem ()

@property (setter=_setViewHandlesEvents:) BOOL _viewHandlesEvents;

@end

viewHandlesEvents设置为false,NSMenuItem中自定义视图的背景将被选择并显示(在某种程度上)如预期。

标签和其他子视图对选择的反应仍然存在问题。文本视图文本未正确更改颜色。

let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
menuItem.view = label
menuItem._viewHandlesEvents = false

NSMenuItemView with <code>_viewHandlesEvents</code> set to false

互联网上还有一些关于_viewHandlesEvents的参考资料:


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