NSColor系统颜色在切换深色/浅色模式时未更改。

4

我想在NSViewController中实现深色/浅色模式下图片颜色的切换。

我使用以下代码来改变图片的颜色:

- (NSImage *)image:(NSImage *)image withColour:(NSColor *)colour
{   
    NSImage *img = image.copy;
    [img lockFocus];
    [colour set];
    NSRect imageRect = NSMakeRect(0, 0, img.size.width, img.size.height);
    NSRectFillUsingOperation(imageRect, NSCompositingOperationSourceAtop);
    [img unlockFocus];
    return img;
}

我已尝试从viewWillLayout调用此方法。
self.help1Image.image = [self image:self.help1Image.image withColour:[NSColor systemRedColor]];

但是似乎系统颜色总是返回相同的RGB值。

我还尝试侦听通知AppleInterfaceThemeChangedNotification,但即使在此处,RGB值也保持不变1.000000 0.231373 0.188235

[[NSDistributedNotificationCenter defaultCenter] addObserverForName:@"AppleInterfaceThemeChangedNotification"
                                                             object:nil
                                                              queue:nil
                                                         usingBlock:^(NSNotification * _Nonnull note) {

                                                             NSLog(@"AppleInterfaceThemeChangedNotification");
                                                             self.help1Image.image = [self image:self.help1Image.image withColour:[NSColor systemRedColor]];

                                                             NSColorSpace *colorSpace = [NSColorSpace sRGBColorSpace];
                                                             NSColor *testColor = [[NSColor systemBlueColor] colorUsingColorSpace:colorSpace];
                                                             CGFloat red = [testColor redComponent];
                                                             CGFloat green = [testColor greenComponent];
                                                             CGFloat blue = [testColor blueComponent];
                                                             NSLog(@"%f %f %f", red, green, blue);
                                                         }];

我已经在一个NSButtonCell子类中通过覆盖layout方法,成功实现了功能,但在NSViewController中无法做到。

2个回答

9

首先,请查看文档部分“使用特定方法更新自定义视图”这里。它说:

当用户更改系统外观时,系统会自动请求每个窗口和视图重新绘制自己。在此过程中,系统调用了几种适用于 macOS 和 iOS 的众所周知的方法,列在以下表格中,用于更新您的内容。在调用这些方法之前,系统会更新 trait 环境,因此,如果您在这些方法中进行了所有外观敏感的更改,则应用程序将正确地更新自己。

但是,该表格中没有列出任何NSViewController方法。

由于视图的外观可以独立于当前或“系统”外观,因此在您的视图控制器中对外观更改做出反应的最佳方法是要么观察视图的 effectiveAppearance 属性,要么在 [NSView viewDidChangeEffectiveAppearance] 中执行某些操作。

- (void)viewDidLoad 
{
    [self addObserver:self forKeyPath:@"view.effectiveAppearance" options:0 context:nil];
}

// ...

- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
    if ([keyPath isEqualToString:@"view.effectiveAppearance"])
    {
// ...

NSAppearance 具有独立于系统外观并由 Cocoa 在上述方法中更新的 currentAppearance 属性。在其他任何地方,您都需要自行检查其是否正确。惯用的方式再次是通过视图的 effectiveAppearance:

[NSAppearance setCurrentAppearance:someView.effectiveAppearance];

所以,在你的情况下,以下内容对我很有效:

- (void)viewDidLoad 
{
    [super viewDidLoad];

    [self addObserver:self forKeyPath:@"view.effectiveAppearance" options:0 context:nil];
}

-(void)viewDidLayout
{
    self.help1Image.image = [self image:self.help1Image.image withColour:[NSColor systemRedColor]];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"view.effectiveAppearance"])
    {
                [NSAppearance setCurrentAppearance:self.view.effectiveAppearance];

                self.help1Image.image = [self image:self.help1Image.image withColour:[NSColor systemRedColor]];
    }
}

3
非常感谢您的回答。关键在于 [NSAppearance setCurrentAppearance:self.view.effectiveAppearance]; 我可以在我提问中发布的通知中设置它,之后颜色就正确了。 - Darren
setCurrentAppearance自macOS 11开始已经被弃用,是否有人找到了替代方案? - tipa

0
假设您有一个带有一些子视图的NSScrollView,这些子视图驻留在此ScrollView的contentView中,并且您能够对NSScrollView进行子类化..

..那么您就不需要实现KVO模式,即使它也可以工作。

对于这样的NSScrollView子类,您只需实现..

-(void)viewDidChangeEffectiveAppearance {
    [self.contentView.subviews makeObjectsPerformSelector:@selector(viewDidChangeEffectiveAppearance)];
}

这将相应地转发/执行所有子视图的-viewDidChangeEffectiveAppearance方法。因此,对于那些实现了...

-(void)viewDidChangeEffectiveAppearance {
    NSString *schemeName = self.window.effectiveAppearance.name;
    if ([schemeName containsString:@"Dark"]) {
        //set colors for Dark Scheme here..
    } else {
        //set colors for Aqua Scheme here..
        //or [schemeName containsString:@"Vibrant"];
        //or [schemeName containsString:@"HighContrast"];
    }
} 

-viewDidChangeEffectiveAppearance 在自定义绘图循环中不总是触发到最后一个子视图。例如,当您避免使用-drawRect方法时,很可能是当您从CALayers构建图形类似的结构时。或者它被调用于每个找到的ViewController的子视图(未经测试)。也就是说,文档非常空白,至少应该知道这个方法仅存在是为了避免@selector失败,从而使您可以在NSViewsubviews数组上调用-makeObjectsPerformSelector:@selector(viewDidChangeEffectiveAppearance)。不太确定为什么苹果选择了一个固定的方法而不是一个协议,很可能是为了避免在有效调用之前进行conformsToProtocolrespondsToSelector调用。

此外,请注意在默认预定义的-viewDidChangeEffectiveAppearance方法中,您将请求self.window.effectiveAppearance。为什么不要求NSAppearance.currentAppearance?因为除非您的系统真正更改,否则NSAppearance.currentAppearance将不会反映更改,但是self.window.effectiveAppearance将提供正在使用的正确方案。
上述解决方案的好处是规避了setCurrentAppearance停用的问题。您需要负责将这些调用级联到子视图中,特别是在以编程方式而不是使用InterfaceBuilder实现子视图时。

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