I wrote a post about “Dark Mode adaptation for iOS” in iOS 13

Dark Mode for macOS 10.14

When dark mode is active, colors, images, and behavior can be updated on the code – so that the application can adapt automatically.

In macOS and iOS, users can choose to adopt a system-wide bright or dark look. This dark look, known as dark Mode, implements an interface style that many applications already use. Users can choose the bright or dark look of the interface they like, and can choose to switch their interface based on ambient lighting conditions or a specific schedule.

Image from Apple

choosemodel(Light color/dark/automatic)

Go to the ‘General’ TAB of ‘System Preferences’ – you can select the corresponding mode (light/Dark/Automatic) ~

‘Universal’ for ‘System preferences’

Various modes (light/Dark/Auto) and selected effects are as follows:

Light color

dark

Automatic – according to ‘according to ambient lighting conditions’ or’ specific schedule ‘

About system color

MacOS, iOS, tvOS system colors:



macOS system Colors



iOS system Colors



tvOS system Colors


In the Nib control, view the color:

In the ‘viewcontroller.swift’ file :(write the following logic in the viewDidAppear method)

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 array let total_W: CGFloat = self.view.window? .contentView? .frame.size. Width?? 0// Get the window width required in 'viewDidAppear' let total_W: CGFloat = 1500.0 CGFloat = 25.0 let countNum: CGFloat = 5 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]// title -- the color tf.backgroundcolor =  NSColor.red } }Copy the code

Effect:

  • 1. In ‘light’ mode, start project — start App

    1-1. Start App in ‘Light’ mode

    1-2. Start App in ‘Light’ mode, and then switch to ‘Dark’ mode in ‘General’

  • 2. In ‘Dark’ mode, run project — start App

    2-1. Launch App in ‘Dark’ mode

    2-2. Start App in ‘Dark’ mode, and then switch to ‘light’ mode in ‘General’

Conclusion: a. Some colors show differently in ‘dark’ mode and ‘light’ mode! (labelColor, underPageBackgroundColor, selectedControlTextColor, etc…) B. There are still a few colors that will change when switching the ‘dark ‘/’ light’ mode after the App runs! (Separate color, secondaryLabelColor, tertiaryLabelColor, quaternaryLabelColor, etc.)


[A]. Set colors on the UI: Light and dark interface modes use very different palettes. Colors that work well in lighter colors may be hard to see in darker colors, and vice versa. An adaptive color object returns different color values for different interface modes.

There are two ways to create an adaptive color object:

  • Choose semantic colors instead of fixed color values. When configuring UI elements, select a color with a name like labelColor. These semantic colors convey the intended purpose of the color, not a specific color value. When used for their intended purpose, they will be rendered with a color value appropriate to the current setting. For a complete list of semantic color names, see NSColor and UIColor. Use semantic colors for custom UI elements so that they match the look of other AppKit views! And the UI Element Colors

  • Define the desired custom colors in the Asset Catalog. When you need a specific color, create it as a color asset. In the asset defined, specify different color values for light and dark looks. You can also specify a high-contrast version of the color. Add custom colors to ‘assets.xcassets’ :(click + and select the ‘Color Set’ item)

    Select the ‘Color Set’ item

    Configure colors for each mode

    Note: Use the Any Appearance variable to specify the color value to use on older systems that do not support dark mode.

    When the code is used, load the color by name:

    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")) .backgroundColor = useColor? .cgColor }Copy the code

    Effect: In ‘light’ mode and ‘dark’ mode, start the App — color is different, switch between ‘light’ mode and ‘Dark’ mode after starting the App — color does not change

    Launch the App in ‘light’ mode

    Launch App in ‘dark’ mode

    When you create a color object from a Color Asset, you do not have to recreate the object when the current look and feel changes. Each time a fill or stroke color is set for the drawing, the color object loads a color variable that matches the current environment setting. The same is true for semantic colors such as labelColor, which automatically adapt to the current environment. In contrast, color objects created with fixed component values are not adaptive; You must create a new color object.

When the user changes the look and feel of the system, each window and view is automatically asked to redraw itself. During this process, the system calls several common macOS and iOS methods listed in the table below to update the content. If cosmetic-sensitive changes are made outside of these methods, the application may not be able to properly draw its content for the current environment. The solution is to move code that is cosmetically sensitive to changes into these methods.

Classes and methods

🌰 example

Define a custom view class GYHDefineView:

In the ‘gyhdefineview.swift’ file, override the draw method :(put the code that makes the cosmetic-sensitive changes there)

import Cocoa class GYHDefineView: NSView { override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) // Drawing code here. self.wantsLayer = true //let useColor = NSColor(named: Let useColor = NSColor(named: nscolor.name ("GYHViewColor")) self.layer? .backgroundColor = useColor? .cgColor } }Copy the code

Use the GYHDefineView instance ~ in the ‘viewController.swift’ file

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

Effect: After launching the App, switch between ‘light’ mode and ‘dark’ mode — the color changes


[B]. Configure the corresponding display image in ‘light ‘/’ dark’ mode for the image (switch between ‘light’ mode and ‘dark’ mode after starting the App — the display image will change)

Configure the corresponding display image in ‘light ‘/’ dark’ mode :(below, set ‘Appearance’)

When ‘Appearance’ is not configured

The image for ‘Appearance’ is configured

Code use:

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")) .backgroundColor = useColor? .cgColor let imgV = NSImageView(frame: NSMakeRect(20, 10, 150, 50)) self.view .addSubview(imgV) imgV.image = NSImage(named: "Picture") }Copy the code

Effect: After launching the App, switch between ‘light’ mode and ‘dark’ mode — the display image changes

For more, see Providing Images for Different Appearances


[C]. Code to determine whether the current is in dark mode:

/ / determine whether - the current pattern for diablo 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 / / 'dark' model}} return false / / 'light' model}Copy the code

More enumerations of 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 * / @ the available (macOS 10.14. *) public static let accessibilityHighContrastAqua: NSAppearance. Name @ the available (macOS 10.14. *) public static let accessibilityHighContrastDarkAqua: NSAppearance. Name @ the available (macOS 10.14. *) public static let accessibilityHighContrastVibrantLight: NSAppearance. Name @ the available (macOS 10.14. *) public static let accessibilityHighContrastVibrantDark: NSAppearance. Name}Copy the code


[D]. Set nsapplication.shared. appearance or NSApp. Appearance to ‘dark’ or ‘light’

// set -- 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) } }Copy the code


[E]. Select temporarily Out of diablo Mode (Opt Out of the Dark Mode) on the info. In the file, add ‘NSRequiresAquaSystemAppearance’ key and set its Boolean type to YES:

For ‘info. The plist file add NSRequiresAquaSystemAppearance item, and select to YES

For ‘info. Plist file after NSRequiresAquaSystemAppearance item set to YES

After running the App, the interface is displayed in ‘light color’ mode ~

The code can still be set to ‘dark’ or ‘light’ mode (by setting the nsApplication.shared. appearance or NSApp. Appearance attribute).


[F]. Switch between ‘light’ mode and ‘dark’ mode :(listening via NSApp)

If an application has code that is not part of an NSView and cannot use the preferred methods listed above, it can observe the effecveAppearance property of the application and manually update currentAppearance.

var observation: NSKeyValueObservation? // Observe the attributesCopy the code

Listening code:

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 / / listen to the current model, the corresponding operation If app. EffectiveAppearance. Name = = NSAppearance. Name the aqua {/ / 'light' model, response operation} else if app. EffectiveAppearance. Name = = NSAppearance. Name. DarkAqua {/ / 'dark' model, the operation of the response} # if available (OS x 11.0, *) {/ / macOS 11.0 more than just support app. EffectiveAppearance. PerformAsCurrentDrawingAppearance {/ / Invoke your non - the view code that Need to be aware of the // change in appearance. //Copy the code

Listen to the current mode (‘ light ‘mode or’ dark ‘mode) – do the same again!


[G].Pay attention to
  • Visual effects views increase the transparency of the background view, giving the UI more Visual depth than when the background is opaque.

    • In macOS, an NSVisualEffectView is configured with appropriate materials based on how views are used in the interface. For example, when using a visual view as the sidebar interface background, materials used NSVisualEffectMaterialSidebar configure it.
    • In iOS, configure a UIVisualEffectView with specific frosted glass and blur effects (shaker screen) to create the look you want. Blurring effects define the apparent thickness of the background view, while frosted glass effects adjust the appearance of specific types of content to ensure that they remain visible. For example, when you view contains tags, select UIVibrancyEffectStyleLabel style or other tags related frosted glass is one of the options.
  • During Appearance Transitions, your application will be asked to redraw everything when the user interface Transitions between ‘light’ and ‘dark’ modes. While the system manages the drawing process, it relies on a few points of custom code in that process. The code must be as fast as possible and cannot perform tasks unrelated to appearance changes. On macOS, AppKit typically creates transition animations when the look and feel changes, but it aborts them if the application takes too long to redraw itself.


Refer to the article

Supporting Dark Mode in Your Interface:Developer.apple.com/documentati…

Choosing a Specific Appearance for macOS App:developer.apple.com/documentati…

Stackoverflow | neatly and dark mode be detected on macOS 10.14?






goyohol’s essay