[SC]()

iOS. Apple. Indies. Plus Things.

Dynamic and Semantic Colors - Four for One

// Written by Jordan Morgan // Feb 25th, 2022 // Read it in about 2 minutes // RE: UIKit

This post is brought to you by Emerge Tools, the best way to build on mobile.

Colors are complicated. Beneath the sea of color theory, hitting contrast ratios and settling on forty-eight different versions of a gray (or grey 🇬🇧👋) — iOS 13 “hold my beer”‘d the whole situation by introducing Dark Mode.

Semantic and dynamic colors on iOS made this easy to deal with though, no matter if you addressed the concern by using Apple vended defaults or rolling your own. Instead of juggling a panoply of colors, we can lean on their “meaning” instead of their RGB, HSL or hex value:

var body: some View {
    Text("Use system colors")
        .foregroundColor(Color(uiColor: .label)) // Instead of #D2D2D2 or whatever
}

Many of us do the same thing by creating colors programmatically:

var body: some View {
    Text("Use system colors")
        .foregroundColor(Color(uiColor: customLabel))
}

private var customLabel: UIColor {
    get {
        UIColor { traitCollection in
            switch traitCollection.userInterfaceStyle {
            case .dark: return customDark
            case .light: return customLight
            case .unspecified: return customLight
            @unknown default: fatalError()
            }
        }
    }
}

Or, maybe turning to the GUI in an asset catalog is more your style:

The color asset catalog in Xcode.

Two of those examples gave us two context-aware colors for light and dark mode. Can you spot which one gave us…four?

A lot of folks forget there are other situations where you might want to adapt color. One of them? When users pop this little toggle on:

The increase contrast setting in iOS.

Right away, you can see the Settings app adapt - even though it doesn’t use “new” colors under the hood, it doesn’t apply the same values for those colors it previously did:

The settings app on iOS with increased contrast on.

So, one color has now become four:

  1. Light style.
  2. Dark style.
  3. Light style, high contrast.
  4. Dark style, high contrast.

This post is basically me saying — ‘“Don’t sleep on high contrast color values!”

You can handle this at the asset catalog level. Just click the “High Contrast” toggle and you’ll have two more options provided to you to account for high contrast scenarios:

An asset catalog color with the increased contrast color option enabled.

Of course, this is trivial to do at the API level as well:

var body: some View {
    Text("Use system colors")
        .foregroundColor(Color(uiColor: customLabel))
}

private var customLabel: UIColor {
    get {
        let highContrast = UIAccessibility.isDarkerSystemColorsEnabled

        UIColor { traitCollection in
            switch traitCollection.userInterfaceStyle {
            case .dark: return highContrast ? : .customHCDark : customDark
            case .light: return highContrast ? : .customHCLight : customLight
            case .unspecified: return customLight
            @unknown default: fatalError()
            }
        }
    }
}

And as we saw above, Apple’s system colors work this way, too. Here’s another example from the Human Interface Guidelines:

The human interface guidelines showing system colors and how they adapt to high contrast settings.

So remember, the next time you’re picking out a color, it’s never one color. At the absolute minimum, it’s two. For most colors, that number should be four.

Until next time ✌️.

···

Spot an issue, anything to add?

Reach Out.