IOS 13 was released at WWDC 19 today. Let’s take a look at how to adapt DarkMode

First let’s take a look at the renderings

How to adapt to DarkMode

DarkMode mainly ADAPTS from two aspects, one is color and the other is picture. There are not many adaption codes. Next, let’s have a look at the specific operation.

Color adaptation

Before iOS 13, UIColor could only represent one color. Starting with iOS 13, UIColor is a dynamic color that can have different colors in LightMode and DarkMode.

IOS 13 has added a lot of dynamic colors to UIColor, so let’s see what we can achieve with the colors provided by the system.

// UIColor added color
@available(iOS 13.0, *)
open class var systemBackground: UIColor { get }
@available(iOS 13.0, *)
open class var label: UIColor { get }
@available(iOS 13.0, *)
open class var placeholderText: UIColor { get}... view.backgroundColor =UIColor.systemBackground
label.textColor = UIColor.label
placeholderLabel.textColor = UIColor.placeholderText
Copy the code

How about that? It looks the same as setting a color before iOS 13. With this dynamic color, the system directly completes the adaptation work for us, isn’t it very convenient?

How do I create a dynamic UIColor myself

As mentioned above, the system provides some dynamic colors for us to use, but in normal development, the colors provided by the system are definitely not enough, so we have to create our own dynamic colors.

In iOS 13, UIColor added an initialization method that we can use to create dynamic colors.

@available(iOS 13.0*),public init(dynamicProvider: @escaping (UITraitCollection) - >UIColor)
Copy the code

This method requires passing in a closure that triggers the callback when the system switches between LightMode and DarkMode.

This closure returns a UITraitCollection class whose userInterfaceStyle property we will use. UserInterfaceStyle is an enumeration declared as follows

@available(iOS 12.0*),public enum UIUserInterfaceStyle : Int {
    case unspecified
    case light
    case dark
}
Copy the code

This enumeration will tell us if the current is LightMode or DarkMode


Now we create two Uicolors and assign them to view.backgroundColor and label as follows

let backgroundColor = UIColor { (trainCollection) -> UIColor in
    if trainCollection.userInterfaceStyle == .dark {
        return UIColor.black
    } else {
        return UIColor.white
    }
}
view.backgroundColor = backgroundColor

let labelColor = UIColor { (trainCollection) -> UIColor in
    if trainCollection.userInterfaceStyle == .dark {
        return UIColor.white
    } else {
        return UIColor.black
    }
}
label.textColor = labelColor
Copy the code

Now that we’ve done the matching of the background color and the text color in the GIF, let’s see how the image fits

Graphics adapter

Open assets.xcassets and drag the image in to see a page that looks like this

Then we click on the last bar on the right toolbar and click on bench selection Any, Dark, as shown in the picture

Let’s drag in the DarkMode image as shown

Finally, we add the ImageView code

imageView.image = UIImage(named: "icon")
Copy the code

Now we have finished the DarkMode matching of color and image, isn’t it easy (manual funny)

How to get current mode (Light or Dark)

We can see that no matter the color or the picture, the adaptation is completed by the system, we do not care about what style is now.

But in some cases, we may need to make some other adaptation based on the current style, and we need to know what the current style is.

. We can call in UIViewController or UIView traitCollection userInterfaceStyle to obtain the current view of style, the code is as follows

if trainCollection.userInterfaceStyle == .dark {
    // Dark
} else {
    // Light
}
Copy the code

So when do we need to use this method for adaptation? For example, when we use CGColor, it says that UIColor becomes a dynamic color in iOS 13, but CGColor can only represent a single color, so when we use CGColor, We can use the above method to do adaptation.

color

We also have another adaptation method for CGColor, the code is as follows

let resolvedColor = labelColor.resolvedColor(with: traitCollection)
layer.borderColor = resolvedColor.cgColor
Copy the code

The resolvedColor method returns the corresponding color based on the traitCollection passed in.

The picture

We have a similar method for UIImage, which looks like this

let image = UIImage(named: "icon")
letresovledImage = image? .imageAsset? .image(with: traitCollection)Copy the code

How do I listen for schema changes

Above we said how to get the current mode, but we need to use it in conjunction with the listener method. When switching light dark mode, we need to execute the above code again. The system provides us with a callback method that is triggered when the Light Dark switches.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
        // Adapt code}}Copy the code

If you find it difficult to adapt CGColor, try XYColor.

How to change the current pattern

We can see in the GIF that the mode of the system is directly changed so that the mode of the App can be changed. However, for some apps with night mode function, if the user turns on the night mode, the dark mode will be forced even if the system is in light mode now.

We can change the current UIViewController or UIView mode with the following code.

overrideUserInterfaceStyle = .dark
print(traitCollection.userInterfaceStyle)  // dark
Copy the code

We can see that after setting overrideUserInterfaceStyle traitCollection. UserInterfaceStyle is after we set the pattern.

Do I need to set every Controller and View

The answer is no. Let’s start with a picture.

However, subsequent controllers still follow the style of the system.

Because apple explanation for overrideUserInterfaceStyle attributes is as follows.

When we override this property on a normal Controlle View, it only affects the current view, it doesn’t affect previous controllers or subsequent controllers.

But when we are on the window set overrideUserInterfaceStyle, will affect all the controller under the window, the view, including the follow-up of the controller.

We went back to the question of just, if the App open mode at night, so we only need to set up is simple overrideUserInterfaceStyle attribute of the window.

Side note: when we created the project with Xcode11, we noticed that the structure of the project changed and the window was moved from AppDelegate to SceneDelegate. How do I get the window from SceneDelegate

// For a brief introduction, in the actual project, this is ok for iOS apps, but for iPadOS apps, we need to check whether the scene state is activeletscene = UIApplication.shared.connectedScenes.first? .delegate as? SceneDelegate scene?.window?.overrideUserInterfaceStyle = .darkCopy the code

Other content

Status Bar

Previously, the Status Bar had two states, Default and lightContent

The Status Bar now has three states, Default, darkContent and lightContent

The current darkContent corresponds to the previous default, which automatically selects darkContent and lightContent as appropriate

UIActivityIndicatorView

The previous UIActivityIndicatorView had three styles whiteLarge, White, and Gray, now all obsolete.

Add two styles, medium and large, and change the indicator color with the color attribute.

How do I print logs during mode switch

Add the following command in Arguments Arguments Passed On Launch.

-UITraitCollectionChangeLoggingEnabled YES


This is how iOS 13 ADAPTS to Dark Mode. Please point out any mistakes.

WWDC Link Implementing Dark Mode on iOS

If you want to know what’s new in iOS 13, you can read this article.