macOS 暗黑模式

之前写了《iOS 13下暗黑(深色)模式的配置》一篇关于‘iOS的暗黑模式适配’的文章~
这次讨论一下macOS环境下的暗黑模式~(macOS 10.14)

暗黑模式活跃状态时,代码上可更新颜色图像行为—以便让应用程序可以自动适应。

在macOS和iOS中,用户可以选择采用全系统的明亮暗黑界面外观。这种被称为“暗黑模式”(dark Mode)的黑色外观,实现了许多应用程序已经采用的界面风格。用户可以选择他们喜欢的明亮暗黑界面外观,也可以根据环境照明条件特定的时间表来选择切换他们的界面

图片来自Apple

选择 模式(浅色/深色/自动)

进入'系统偏好设置'的'通用'项—就可以选择相应的模式(浅色/深色/自动)了~

'系统偏好设置'的'通用'

各种模式(浅色/深色/自动)及选中效果,如下:

浅色
深色
自动—根据'根据环境照明条件'或'特定的时间表'

关于 系统颜色

如下,macOS、iOS、tvOS系统颜色

macOS system Colors
iOS system Colors
tvOS system Colors

讨论下macOS系统颜色,及其在相应模式下的展示效果
在Nib控件中,查看颜色:

查看颜色(Nib控件中查看)

在'ViewController.swift'文件中:(在viewDidAppear方法内书写如下逻辑代码)

override func viewDidAppear() {
   super.viewDidAppear()
   
   let title_color_Arr: [String] = ["NSColor.labelColor", "NSColor.secondaryLabelColor", "NSColor.tertiaryLabelColor", "NSColor.quaternaryLabelColor", "NSColor.systemRed", "NSColor.systemGreen", "NSColor.systemBlue", "NSColor.systemOrange", "NSColor.systemYellow", "NSColor.systemBrown", "NSColor.systemPink", "NSColor.systemPurple", "NSColor.systemTeal", "NSColor.systemIndigo", "NSColor.systemGray", "NSColor.linkColor", "NSColor.placeholderTextColor", "NSColor.windowFrameColor", "NSColor.selectedMenuItemTextColor", "NSColor.alternateSelectedControlTextColor", "NSColor.headerTextColor", "NSColor.separatorColor", "NSColor.gridColor", "NSColor.textColor", "NSColor.textBackgroundColor", "NSColor.selectedTextColor", "NSColor.selectedTextBackgroundColor", "NSColor.selectedTextColor", "NSColor.selectedTextBackgroundColor", "NSColor.unemphasizedSelectedTextBackgroundColor", "NSColor.unemphasizedSelectedTextBackgroundColor", "NSColor.windowBackgroundColor", "NSColor.underPageBackgroundColor", "NSColor.controlBackgroundColor", "NSColor.selectedContentBackgroundColor", "NSColor.unemphasizedSelectedContentBackgroundColor", "NSColor.findHighlightColor", "NSColor.controlColor", "NSColor.controlTextColor", "NSColor.selectedControlColor", "NSColor.selectedControlTextColor", "NSColor.disabledControlTextColor", "NSColor.keyboardFocusIndicatorColor", "NSColor.controlAccentColor"]
   let v_color_Arr: [NSColor] = [NSColor.labelColor, NSColor.secondaryLabelColor, NSColor.tertiaryLabelColor, NSColor.quaternaryLabelColor, NSColor.systemRed, NSColor.systemGreen, NSColor.systemBlue, NSColor.systemOrange, NSColor.systemYellow, NSColor.systemBrown, NSColor.systemPink, NSColor.systemPurple, NSColor.systemTeal, NSColor.systemIndigo, NSColor.systemGray, NSColor.linkColor, NSColor.placeholderTextColor, NSColor.windowFrameColor, NSColor.selectedMenuItemTextColor, NSColor.alternateSelectedControlTextColor, NSColor.headerTextColor, NSColor.separatorColor, NSColor.gridColor, NSColor.textColor, NSColor.textBackgroundColor, NSColor.selectedTextColor, NSColor.selectedTextBackgroundColor, NSColor.selectedTextColor, NSColor.selectedTextBackgroundColor, NSColor.unemphasizedSelectedTextBackgroundColor, NSColor.unemphasizedSelectedTextBackgroundColor, NSColor.windowBackgroundColor, NSColor.underPageBackgroundColor, NSColor.controlBackgroundColor, NSColor.selectedContentBackgroundColor, NSColor.unemphasizedSelectedContentBackgroundColor, NSColor.findHighlightColor, NSColor.controlColor, NSColor.controlTextColor, NSColor.selectedControlColor, NSColor.selectedControlTextColor, NSColor.disabledControlTextColor, NSColor.keyboardFocusIndicatorColor, NSColor.controlAccentColor]
   //NSColor.alternatingContentBackgroundColors    //[NSColor]数组
   
   //let total_W: CGFloat = self.view.window?.contentView?.frame.size.width ?? 0//获取到的窗口宽度——需要在`viewDidAppear`方法中
   let total_W: CGFloat = 1500.0//总宽度
   let margin: CGFloat = 25.0
   let countNum: CGFloat = 5//每一行里面的个数
   let item_W: CGFloat = (total_W - (countNum + 1)*margin)/countNum
   let item_H: CGFloat = 30
   for i in 0..


效果

  • 1.'浅色'模式时,运行工程——启动App
    1-1.'浅色'模式时启动App

    1-1.'浅色'模式时启动App

    1-2.'浅色'模式时启动App,再在'通用"中切为'深色'模式

    1-2.'浅色'模式时启动App,再切为'深色'模式

  • 2.'深色'模式时,运行工程——启动App
    2-1.'深色'模式时启动App

    2-1.'深色'模式时启动App

    2-2.'深色'模式时启动App,再在'通用"中切为'浅色'模式

    2-2.'深色'模式时启动App,再切为'浅色'模式


结论
a.部分颜色在'深色'模式和'浅色'模式展示不同!(labelColorunderPageBackgroundColorselectedControlTextColor等……)
b.还有少部分颜色在App运行后,再切换'深色'/'浅色'模式时会有变化!(separatorColorsecondaryLabelColortertiaryLabelColorquaternaryLabelColor等……)



[A].UI上设置颜色
浅色深色界面模式使用非常不同的调色板。在浅色下效果很好的颜色在深色下可能很难被看到,反之亦然。一个自适应颜色对象为不同的界面模式返回不同的颜色值。

如下,有两种方法可以创建自适应颜色对象:

  • 选择语义颜色(semantic colors),而不是固定的颜色值。在配置UI元素时,选择具有labelColor之类名称的颜色。这些语义颜色传达颜色预期用途,而不是特定的颜色值。当将它们用于预期目的时,它们将以适合当前设置颜色值呈现。要获取语义颜色名称的完整列表,请参见NSColor和UIColor。
    为自定义UI元素使用语义颜色,以便它们与其他AppKit视图的外观匹配!参 UI Element Colors

  • 在资产目录(asset catalog)中定义所需自定义颜色。当你需要一个特定的颜色,创建它作为一个颜色资产(color asset)。在定义的资产中,为浅色深色的外观指定相应的不同颜色值。还可以指定颜色的高对比度版本。
    在'Assets.xcassets'中添加自定义颜色:(点击+,选择'Color Set'项

    选择'Color Set'项
    为各种模式配置相应颜色

    注:使用Any Appearance变量指定不支持暗黑模式旧系统使用的颜色值。

    代码使用时,按名称加载该颜色:

    override func viewDidLoad() {
      super.viewDidLoad()
      let defineColor_V = NSView(frame: NSMakeRect(20, 10, 150, 50))
      self.view .addSubview(defineColor_V)
      defineColor_V.wantsLayer = true
      //let useColor = NSColor(named: "GYHViewColor")//有效果
      let useColor = NSColor(named: NSColor.Name("GYHViewColor"))//官方推荐的写法
      defineColor_V.layer?.backgroundColor = useColor?.cgColor
      
    }
    

    效果:在'浅色'模式和'深色'模式下,启动App—颜色不同、启动App后切换'浅色'模式和'深色'模式颜色 不会发生变化

    '浅色'模式时启动App
    '深色'模式时启动App

    从颜色资产(color asset)创建一个颜色对象时,不必在当前外观发生变化时重新创建该对象。每次设置绘图的填充(fill)描边(stroke)颜色时,颜色对象就会加载与当前环境设置匹配颜色变量。对于语义颜色(如labelColor)也是如此,它会自动适应当前环境。相比之下,使用固定组件值创建的颜色对象自适应;你必须创建一个颜色对象


Tips:使用特定方法更新 自定义视图颜色
当用户更改系统外观时,系统会自动要求每个窗口和视图重新绘制自己。在此过程中,系统会调用下表中列出的几个macOS和iOS常用方法更新内容
在这些方法之外进行了外观敏感更改,那么应用程序可能无法为当前环境正确绘制其内容。解决方案是:将外观敏感更改代码移入到这些方法中。

类及其方法
例子

自定义一个视图类GYHDefineView:

GYHDefineView

在'GYHDefineView.swift'文件中,重写draw方法:(进行外观敏感更改代码放在里面)

import Cocoa

class GYHDefineView: NSView {

   override func draw(_ dirtyRect: NSRect) {
       super.draw(dirtyRect)

       // Drawing code here.
       self.wantsLayer = true
       //let useColor = NSColor(named: "GYHViewColor")//有效果
       let useColor = NSColor(named: NSColor.Name("GYHViewColor"))//官方推荐的写法
       self.layer?.backgroundColor = useColor?.cgColor
   }
   
}

在'ViewController.swift'文件中使用GYHDefineView实例~

override func viewDidLoad() {
 super.viewDidLoad()
 let defineColor_V = GYHDefineView(frame: NSMakeRect(20, 10, 150, 50))
 self.view .addSubview(defineColor_V)
 
}

效果:启动App后切换'浅色'模式和'深色'模式颜色 会发生变化



[B].图片配置相应'浅色'/'深色'模式下的展示图(启动App后切换'浅色'模式和'深色'模式展示图 会发生变化

图片配置相应'浅色'/'深色'模式下的展示图:(如下,配置好'Appearance')

未配置'Appearance'时
配置好了'Appearance'的图片

代码使用

override func viewDidLoad() {
  super.viewDidLoad()
  let defineColor_V = NSView(frame: NSMakeRect(20, 10, 150, 50))
  self.view .addSubview(defineColor_V)
  defineColor_V.wantsLayer = true
  //let useColor = NSColor(named: "GYHViewColor")//有效果
  let useColor = NSColor(named: NSColor.Name("GYHViewColor"))//官方推荐的写法
  defineColor_V.layer?.backgroundColor = useColor?.cgColor
  
  let imgV = NSImageView(frame: NSMakeRect(20, 10, 150, 50))
  self.view .addSubview(imgV)
  imgV.image = NSImage(named: "Picture")
}

效果:启动App后切换'浅色'模式和'深色'模式展示图 会发生变化


更多,参考《Providing Images for Different Appearances》



[C].代码判断当前是否为暗黑模式:

//判断——当前是否为暗黑模式
func checkIsDark() -> Bool {
    let apperance = NSApp.effectiveAppearance;
    if #available(macOS 10.14, *) {
        if apperance .bestMatch(from: [NSAppearance.Name.darkAqua, NSAppearance.Name.aqua]) == NSAppearance.Name.darkAqua {
            return true  //'深色'模式
        }
    }
    return false  //'浅色'模式
}

更多NSAppearance.Name枚举值

extension NSAppearance.Name {

    
    @available(macOS 10.9, *)
    public static let aqua: NSAppearance.Name

    @available(macOS 10.14, *)
    public static let darkAqua: NSAppearance.Name

    
    @available(macOS, introduced: 10.9, deprecated: 10.10, message: "Light content should use the default Aqua apppearance.")
    public static let lightContent: NSAppearance.Name

    
    /* The following two Vibrant appearances should only be set on an NSVisualEffectView, or one of its container subviews.
     */
    @available(macOS 10.10, *)
    public static let vibrantDark: NSAppearance.Name

    @available(macOS 10.10, *)
    public static let vibrantLight: NSAppearance.Name

    
    /* The following appearance names are for matching using bestMatchFromAppearancesWithNames:
       Passing any of them to appearanceNamed: will return NULL
     */
    @available(macOS 10.14, *)
    public static let accessibilityHighContrastAqua: NSAppearance.Name

    @available(macOS 10.14, *)
    public static let accessibilityHighContrastDarkAqua: NSAppearance.Name

    @available(macOS 10.14, *)
    public static let accessibilityHighContrastVibrantLight: NSAppearance.Name

    @available(macOS 10.14, *)
    public static let accessibilityHighContrastVibrantDark: NSAppearance.Name
}


[D].通过代码设置当前模式'深色'模式'浅色'模式
设置NSApplication .shared.appearanceNSApp.appearance属性

//设置——'深色'模式、'浅色'模式
func setNowToIsDark(isDark: Bool) {
    if isDark {
        NSApplication .shared.appearance = NSAppearance(named: NSAppearance.Name.darkAqua)
        //NSApp.appearance = NSAppearance(named: NSAppearance.Name.darkAqua)
    } else {
        NSApplication .shared.appearance = NSAppearance(named: NSAppearance.Name.aqua)
        //NSApp.appearance = NSAppearance(named: NSAppearance.Name.aqua)
    }
}



[E].选择暂时退出 暗黑模式Opt Out of Dark Mode
info.plist文件中,添加'NSRequiresAquaSystemAppearance'设置Boolean型为YES

为'info.plist'文件添加NSRequiresAquaSystemAppearance项,并选择为YES
对'info.plist'文件的NSRequiresAquaSystemAppearance项设为YES后

此时运行App后,界面是以'浅色'模式展示~

但此时代码上,仍然可以设置为'深色'模式'浅色'模式(通过设置NSApplication .shared.appearanceNSApp.appearance属性



[F].监听 '浅色'模式和'深色'模式切换:(通过NSApp来监听)

如果应用程序有不属于NSView的代码,并且不能使用上面列表列出的首选方法,它可以观察 应用程序effecveappearance属性并手动更新currentAppearance

var observation: NSKeyValueObservation?//观察的属性

监听代码

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Insert code here to initialize your application
    
    observation = NSApp.observe(\.effectiveAppearance) { (app, _) in
        //print("app.effectiveAppearance:\(app.effectiveAppearance.name)")//NSAppearanceName(_rawValue: NSAppearanceNameDarkAqua)
        //print("app.effectiveAppearance:\(app.effectiveAppearance.name.rawValue)")//NSAppearanceNameDarkAqua
        //监听到的当前模式——进行相应操作
        if app.effectiveAppearance.name == NSAppearance.Name.aqua {
            //'浅色'模式——响应的操作
            
        } else if app.effectiveAppearance.name == NSAppearance.Name.darkAqua {
            //'深色'模式——响应的操作
            
        }
        
        if #available(OSX 11.0, *) {//macOS 11.0以上才支持
            app.effectiveAppearance.performAsCurrentDrawingAppearance {
                // Invoke your non-view code that needs to be aware of the
                // change in appearance.
                //监听到的当前模式——进行相应操作
                
                
            }
        }
    }
}

监听到的当前模式('浅色'模式或'深色'模式)——再进行相应操作



[G].注意
  • 根据使用目的选择视觉效果材料(Visual-Effect Materials)
    视觉效果视图增加了背景视图的透明度,这比背景不透明时给UI更多的视觉深度
    • macOS中,根据在界面中使用视图的方式,用适当的材料配置一个NSVisualEffectView。 例如,当使用一个视觉效果视图作为侧边栏界面背景时,使用NSVisualEffectMaterialSidebar材料配置它。
    • iOS中,配置一个具有特定的毛玻璃模糊效果(震颤式画面)的UIVisualEffectView来创建你想要的外观。模糊效果定义背景视图的表观厚度,而毛玻璃效果调整特定类型的内容的外观,以确保它们保持可见。例如,当你的视图包含标签时,选择UIVibrancyEffectStyleLabel样式或其他标签相关的毛玻璃选项之一。
  • 外观过渡动画(Appearance Transitions)期间,避免 消耗昂贵任务
    当用户界面在'浅色'模式'深色'模式之间切换时,系统会要求你的应用程序重新绘制所有内容。虽然系统管理绘图过程,但它依赖于在该过程中自定义代码几个点。代码必须尽可能快并且不能执行与外观更改无关任务。在macOS中,AppKit通常会在外观更改时创建过渡动画,但如果应用程序重新绘制自己的时间太长,它会中止这些动画





参考文章

Supporting Dark Mode in Your Interface:https://developer.apple.com/documentation/uikit/appearance_customization/supporting_dark_mode_in_your_interface
Choosing a Specific Appearance for Your macOS App:https://developer.apple.com/documentation/appkit/nsappearancecustomization/choosing_a_specific_appearance_for_your_macos_app

stackoverflow | How can dark mode be detected on macOS 10.14?









goyohol's essay

你可能感兴趣的:(macOS 暗黑模式)