【问题标题】:How can dark mode be detected on macOS 10.14?如何在 macOS 10.14 上检测暗模式?
【发布时间】:2019-01-11 07:37:20
【问题描述】:

在 macOS 10.14 中,用户可以选择采用系统范围的浅色或深色外观,我需要根据当前模式手动调整一些颜色。

【问题讨论】:

    标签: objective-c macos appearance macos-mojave


    【解决方案1】:

    由于您通常通过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
    • @MuntashirAkon:您不需要将其称为“来自”视图,这无关紧要。您只需要确定上下文的 一个 视图,例如作为备用应用程序的主视图。
    • 您必须从drawRect 调用它吗? viewDidLayout呢?
    • 这段代码曾经为我工作。但在将 Xcode 更新到 11.4 之后,它神秘地不再工作了。在明暗模式下,它都会返回 Aqua 模式。其他人有同样的问题吗?
    • @93sauu 这样做似乎有竞争条件。 NSApp.effectiveAppearance 似乎是检测系统范围设置的更好方法。
    【解决方案2】:

    我已经使用当前外观检查系统是否为 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);
    }
    

    更多信息Dark Mode Documentation

    【讨论】:

    • 对外观变化做出反应的最佳方式是-[NSView viewDidChangeEffectiveAppearance]。您还可以 KVO 视图的 effectiveAppearance 属性,例如,如果您想对视图控制器中的外观更改做出反应。请记住,视图的外观可能与“当前”或系统外观不同。
    • NSAppearance.currentAppearance 在用户在亮模式和暗模式之间切换时并不总是有效,反之亦然。
    • NSAppearance.currentAppearance 返回对象在当前线程上处于活动状态的外观,因此您无法确定,因为当前对象可能分配了AquaDark Aqua 来代替Inherited。所以最好的解决方案是使用someView.effectiveAppearance
    • AppleInterfaceThemeChangedNotification 对我不起作用。而是使用viewDidLayout 调用您的暗/亮模式方法。
    【解决方案3】:

    斯威夫特 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:) 存在并且应该被使用的原因。
    • 感谢您的建议。答案已更新。
    【解决方案4】:

    实际上有 8 个可能的appearances 用于视图,其中 4 个用于普通用途。也就是说,

    1. NSAppearanceNameAqua 灯光模式,
    2. NSAppearanceNameDarkAqua 黑暗模式,
    3. NSAppearanceNameAccessibilityHighContrastAqua 增加对比度的灯光模式(从辅助功能设置),
    4. NSAppearanceNameAccessibilityHighContrastDarkAqua 增加对比度的暗模式。

    直接比较

    appearance.name == NSAppearanceNameDarkAqua;

    如果对比度增加,可能无法检测暗模式。因此,请始终使用bestMatchFromAppearancesWithNames

    最好考虑到高对比度的外观以获得更好的可访问性。

    【讨论】:

      【解决方案5】:

      对我来说,如果我想要一个全局状态,而不是每个视图,并且我无权访问视图,并且我希望收到更新通知,那么这些答案都不起作用。

      解决方案是在主线程中请求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 的回答也有帮助,但似乎比其他人更脆弱。

      【讨论】:

        【解决方案6】:

        要了解应用外观是否为深色,请使用以下代码:

        + (BOOL)isDarkMode {
            NSString *interfaceStyle = [NSUserDefaults.standardUserDefaults valueForKey:@"AppleInterfaceStyle"];
            return [interfaceStyle isEqualToString:@"Dark"];
        }
        

        【讨论】:

        • NSUserDefaults.standardUserDefaults 可能与应用的视图不同。
        猜你喜欢
        • 1970-01-01
        • 2021-07-31
        • 2021-10-14
        • 1970-01-01
        • 1970-01-01
        • 2019-10-17
        • 1970-01-01
        • 2019-05-09
        • 2021-03-25
        相关资源
        最近更新 更多