【发布时间】:2019-01-11 07:37:20
【问题描述】:
在 macOS 10.14 中,用户可以选择采用系统范围的浅色或深色外观,我需要根据当前模式手动调整一些颜色。
【问题讨论】:
标签: objective-c macos appearance macos-mojave
在 macOS 10.14 中,用户可以选择采用系统范围的浅色或深色外观,我需要根据当前模式手动调整一些颜色。
【问题讨论】:
标签: objective-c macos appearance macos-mojave
由于您通常通过effectiveAppearance 获得的实际外观对象是复合外观,因此直接询问其名称可能不是可靠的解决方案。
请求currentAppearance 通常也不是一个好主意,因为视图可能被明确设置为浅色模式,或者您想知道在 drawRect: 之外的视图是亮还是暗模式切换后得到不正确的结果。
我想出的解决方案是这样的:
BOOL appearanceIsDark(NSAppearance * appearance)
{
if (@available(macOS 10.14, *)) {
NSAppearanceName basicAppearance = [appearance bestMatchFromAppearancesWithNames:@[
NSAppearanceNameAqua,
NSAppearanceNameDarkAqua
]];
return [basicAppearance isEqualToString:NSAppearanceNameDarkAqua];
} else {
return NO;
}
}
您可以像appearanceIsDark(someView.effectiveAppearance) 一样使用它,因为如果您明确设置someView.appearance,特定视图的外观可能与另一个视图的外观不同。
您还可以在NSAppearance 上创建一个类别并添加一个- (BOOL)isDark 方法来获取someView.effectiveAppearance.isDark(最好选择一个Apple 将来不太可能使用的名称,例如通过添加供应商前缀)。
【讨论】:
NSApp.mainWindow.effectiveAppearance
drawRect 调用它吗? viewDidLayout呢?
我已经使用当前外观检查系统是否为 10.14
+ (BOOL)isDarkMode {
NSAppearance *appearance = NSAppearance.currentAppearance;
if (@available(*, macOS 10.14)) {
return appearance.name == NSAppearanceNameDarkAqua;
}
return NO;
}
要检测视图中模式的变化,方法是:
- (void)updateLayer;
- (void)drawRect:(NSRect)dirtyRect;
- (void)layout;
- (void)updateConstraints;
要检测视图控制器中模式的变化,方法是:
- (void)updateViewConstraints;
- (void)viewWillLayout;
- (void)viewDidLayout;
使用通知:
// Monitor menu/dock theme changes...
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];
-(void)themeChanged:(NSNotification *) notification {
NSLog (@"%@", notification);
}
【讨论】:
-[NSView viewDidChangeEffectiveAppearance]。您还可以 KVO 视图的 effectiveAppearance 属性,例如,如果您想对视图控制器中的外观更改做出反应。请记住,视图的外观可能与“当前”或系统外观不同。
NSAppearance.currentAppearance 在用户在亮模式和暗模式之间切换时并不总是有效,反之亦然。
NSAppearance.currentAppearance 返回对象在当前线程上处于活动状态的外观,因此您无法确定,因为当前对象可能分配了Aqua 或Dark Aqua 来代替Inherited。所以最好的解决方案是使用someView.effectiveAppearance。
AppleInterfaceThemeChangedNotification 对我不起作用。而是使用viewDidLayout 调用您的暗/亮模式方法。
斯威夫特 4
func isDarkMode(view: NSView) -> Bool {
if #available(OSX 10.14, *) {
return view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
}
return false
}
【讨论】:
NSAppearance.currentAppearance 可能会给出错误的结果(例如,如果你在方法mentioned in this answer 之外调用它并且用户更改了系统外观,NSAppearance.currentAppearance 可能仍会返回旧系统外观)。因此,请始终询问有关其有效外观的具体观点。
appearance.name == .darkAqua 的检查在某些情况下也可能是错误的,因为 实际 外观是“复合”。这就是为什么 bestMatch(from:) 存在并且应该被使用的原因。
实际上有 8 个可能的appearances 用于视图,其中 4 个用于普通用途。也就是说,
NSAppearanceNameAqua 灯光模式,NSAppearanceNameDarkAqua 黑暗模式,NSAppearanceNameAccessibilityHighContrastAqua 增加对比度的灯光模式(从辅助功能设置),NSAppearanceNameAccessibilityHighContrastDarkAqua 增加对比度的暗模式。 直接比较
appearance.name == NSAppearanceNameDarkAqua;
如果对比度增加,可能无法检测暗模式。因此,请始终使用bestMatchFromAppearancesWithNames。
最好考虑到高对比度的外观以获得更好的可访问性。
【讨论】:
对我来说,如果我想要一个全局状态,而不是每个视图,并且我无权访问视图,并且我希望收到更新通知,那么这些答案都不起作用。
解决方案是在主线程中请求NSApp.effectiveAppearance,或者至少在当前回调方法返回系统之后。
所以,首先我必须按照 Saúl Moreno Abril 的指示进行注册,代码如下:
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];
然后在回调方法上写类似
-(void)themeChanged:(NSNotification *) notification {
[self performSelectorOnMainThread:@selector(themeChangedOnMainThread) withObject:nil waitUntilDone:false];
}
然后是实际代码:
- (void) themeChangedOnMainThread {
NSAppearance* appearance = NSApp.effectiveAppearance;
NSString* name = appearance.name;
BOOL dark = [appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]] == NSAppearanceNameDarkAqua;
}
Borzh 的回答也有帮助,但似乎比其他人更脆弱。
【讨论】:
要了解应用外观是否为深色,请使用以下代码:
+ (BOOL)isDarkMode {
NSString *interfaceStyle = [NSUserDefaults.standardUserDefaults valueForKey:@"AppleInterfaceStyle"];
return [interfaceStyle isEqualToString:@"Dark"];
}
【讨论】:
NSUserDefaults.standardUserDefaults 可能与应用的视图不同。