NSColor 系统切换暗/亮模式时颜色不变



我正在尝试通过切换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

首先,在此处查看文档部分"使用特定方法更新自定义视图"。它说:

当用户更改系统外观时,系统会自动要求每个窗口和视图重绘自身。在此过程中,系统会调用下表中列出的适用于 macOS 和 iOS 的几种已知方法来更新您的内容。系统会在调用这些方法之前更新特征环境,因此,如果您在这些方法中进行所有外观敏感更改,您的应用将正确更新自身。

但是,该表中未列出NSViewController方法。

由于视图的外观可以独立于当前或"系统"外观,因此对视图控制器中的外观更改做出反应的最佳方法是 KVO 视图的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具有独立于系统外观的currentAppearance属性,并由Cocoa在上面列出的方法中进行更新。在其他任何地方,您都需要自己检查是否正确。惯用的方式再次是通过视图的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]];
}
}

假设您有一个NSScrollView,其中包含一些驻留在此类 ScrollViewcontentView中的子视图,并且您可以对 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)。不太确定为什么Apple选择固定方法而不是协议,最有可能避免符合协议并在有效调用之前响应ToSelector调用。

另请注意,在默认的预定义-viewDidChangeEffectiveAppearance方法中,您将要求self.window.effectiveAppearance。为什么不要求NSAppearance.currentAppearance?因为NSAppearance.currentAppearance不会反映更改,除非您的系统确实发生了更改,但self.window.effectiveAppearance将使用正确的方案。

上述解决方案具有规避setCurrentAppearance弃用的好处。并且您负责将这些调用级联到您的子视图,特别是当您以编程方式而不是使用 InterfaceBuilder 实现子视图时

最新更新