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..<v_color_Arr.count {
       print(i)
       let x = CGFloat(i%Int(countNum)) * (item_W + margin) + margin
       let y = CGFloat(i/Int(countNum)) * (item_H + margin) + margin
       let showV = NSView(frame: NSMakeRect(x, y, item_W, item_H))
       self.view .addSubview(showV)
       showV.wantsLayer = true
      let showColor = v_color_Arr[i]
      showV.layer?.backgroundColor = showColor.cgColor
       showV.layer?.borderWidth = 2.0; showV.layer?.borderColor = NSColor.red.cgColor

       let lb_margin: CGFloat = 3.0
       let lb_W: CGFloat = item_W
       let lb_H: CGFloat = margin - 2*lb_margin
       let tf = NSTextField(frame: NSMakeRect(showV.frame.minX, showV.frame.minY - lb_margin - lb_H, lb_W, lb_H))
       self.view .addSubview(tf)
       tf.isEditable = false
       tf.stringValue = title_color_Arr[i]//标题——该颜色
       tf.backgroundColor = NSColor.red
   }
   
}


效果

  • 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之类名称的颜色。这些语义颜色传达颜色预期用途,而不是特定的颜色值。当将它们用于预期目的时,它们将以适合当前设置颜色值呈现。要获取语义颜色名称的完整列表,请参见NSColorUIColor
    为自定义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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容