Backwards compatibility for iOS 13 system colors

June 8, 2019

Update (6/13/2019): The original version of this post featured the wrong colors in the ColorCompatibility enum at the end of the post. This has now been fixed.

At WWDC 2019, Apple announced that Dark Mode would be supported on iOS 13. There are some significant changes to UIKit in order to support this - many of them are detailed in the talk Implementing Dark Mode on iOS which I'd highly recommend watching.

One of the changes that makes adopting Dark Mode so easy is the new system colors API from UIColor. On iOS 12 and older, you might have a label you want to make black, and it would work just fine - in fact, black was the default color for UILabels.

label.textColor = UIColor.black

But in Dark Mode, the background will also be black, which means the text won't be visible. In iOS 13+, it's better to use the new system color which will respect the user's color scheme preference:

label.textColor = UIColor.label

label is only one example: there are 36 new color scheme agnostic UIColors available in iOS 13+, organized into two groups:

  1. Element Colors (e.g. label)
  2. Standard Colors (systemBlue, systemGray3, etc).

(Skip to the bottom if you'd like to see a list of the new colors.)

Compatibility

These new colors are all well and good, but most of us with existing apps will still be supporting devices with iOS 12 or lower, at least for a while. This means we'll probably be doing a lot of things like this, using Swift's #available syntax:

if #available(iOS 13, *) {
    label.textColor = .label
} else {
    label.textColor = .black
}

It's a workable solution, but it necessitates changing a lot of code - an if statement for every custom label or background color! For CIFilter.io, I wondered if there was a better way. What if, instead of the if #available, there was a way to abstract the color choice down one level, so we could do something like this?

label.textColor = ColorCompatibility.label

Generating system colors

I wrote a small app (with SwiftUI, no less!) which displays all the system colors in the current color scheme.

System colors (light mode)
Light mode
System colors (dark mode)
Dark mode

This app collects all the UIColor objects - once we have those, we can use their red/green/blue/alpha components to generate the implementation of ColorCompatibility that we want:

enum ColorCompatibility {
    static var label: UIColor {
        if #available(iOS 13, *) {
            return .label
        }
        return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
    }

    static var secondaryLabel: UIColor {
        if #available(iOS 13, *) {
            return .secondaryLabel
        }
        return UIColor(red: 0.9215686274509803, green: 0.9215686274509803, blue: 0.9607843137254902, alpha: 0.6)
    }

    // ... 34 more definitions: full code at the bottom of this post
}

We can then use ColorCompatibility it to set any colors we need.

Note: we can't use @available for these checks, since it doesn't provide a way to check if the current environment is less than a specific app version.

Conclusion

This approach has some great advantages:

  1. This code will compile on iOS 13+, iOS 12 and earlier, and UIKitForMac ✅
  2. Since everything is a computed var, we never pre-store colors, which means that when the user switches color scheme, our app will automatically adapt as the trait collection changes 👍
  3. When we drop iOS 12 support, cleaning this up will be as simple as replacing every instance of ColorCompatibility with UIColor 🎉

Hopefully this makes your app's transition to dark mode easier!

Table of system colors

For those interested in iOS 13+ system colors but not wanting (or not able) to compile the sample app, here's a list of the system colors in light and dark mode, with their hex codes and RGBA values:

Light Mode

NameColorHex StringRGBA
label
#000000ffrgba(0.0, 0.0, 0.0, 1.0)
secondaryLabel
#3c3c4399rgba(60.0, 60.0, 67.0, 0.6)
tertiaryLabel
#3c3c434crgba(60.0, 60.0, 67.0, 0.3)
quaternaryLabel
#3c3c432drgba(60.0, 60.0, 67.0, 0.18)
systemFill
#78788033rgba(120.0, 120.0, 128.0, 0.2)
secondarySystemFill
#78788028rgba(120.0, 120.0, 128.0, 0.16)
tertiarySystemFill
#7676801ergba(118.0, 118.0, 128.0, 0.12)
quaternarySystemFill
#74748014rgba(116.0, 116.0, 128.0, 0.08)
placeholderText
#3c3c434crgba(60.0, 60.0, 67.0, 0.3)
systemBackground
#ffffffffrgba(255.0, 255.0, 255.0, 1.0)
secondarySystemBackground
#f2f2f7ffrgba(242.0, 242.0, 247.0, 1.0)
tertiarySystemBackground
#ffffffffrgba(255.0, 255.0, 255.0, 1.0)
systemGroupedBackground
#f2f2f7ffrgba(242.0, 242.0, 247.0, 1.0)
secondarySystemGroupedBackground
#ffffffffrgba(255.0, 255.0, 255.0, 1.0)
tertiarySystemGroupedBackground
#f2f2f7ffrgba(242.0, 242.0, 247.0, 1.0)
separator
#3c3c4349rgba(60.0, 60.0, 67.0, 0.29)
opaqueSeparator
#c6c6c8ffrgba(198.0, 198.0, 200.0, 1.0)
link
#007affffrgba(0.0, 122.0, 255.0, 1.0)
darkText
#000000ffrgba(0.0, 0.0, 0.0, 1.0)
lightText
#ffffff99rgba(255.0, 255.0, 255.0, 0.6)
systemBlue
#007affffrgba(0.0, 122.0, 255.0, 1.0)
systemBrown
#a2845effrgba(162.0, 132.0, 94.0, 1.0)
systemGreen
#34c759ffrgba(52.0, 199.0, 89.0, 1.0)
systemIndigo
#5856d6ffrgba(88.0, 86.0, 214.0, 1.0)
systemOrange
#ff9500ffrgba(255.0, 149.0, 0.0, 1.0)
systemPink
#ff2d55ffrgba(255.0, 45.0, 85.0, 1.0)
systemPurple
#af52deffrgba(175.0, 82.0, 222.0, 1.0)
systemRed
#ff3b30ffrgba(255.0, 59.0, 48.0, 1.0)
systemTeal
#5ac8faffrgba(90.0, 200.0, 250.0, 1.0)
systemYellow
#ffcc00ffrgba(255.0, 204.0, 0.0, 1.0)
systemGray
#8e8e93ffrgba(142.0, 142.0, 147.0, 1.0)
systemGray2
#aeaeb2ffrgba(174.0, 174.0, 178.0, 1.0)
systemGray3
#c7c7ccffrgba(199.0, 199.0, 204.0, 1.0)
systemGray4
#d1d1d6ffrgba(209.0, 209.0, 214.0, 1.0)
systemGray5
#e5e5eaffrgba(229.0, 229.0, 234.0, 1.0)
systemGray6
#f2f2f7ffrgba(242.0, 242.0, 247.0, 1.0)

Dark Mode

NameColorHex StringRGBA
label
#ffffffffrgba(255.0, 255.0, 255.0, 1.0)
secondaryLabel
#ebebf599rgba(235.0, 235.0, 245.0, 0.6)
tertiaryLabel
#ebebf54crgba(235.0, 235.0, 245.0, 0.3)
quaternaryLabel
#ebebf528rgba(235.0, 235.0, 245.0, 0.16)
systemFill
#7878805brgba(120.0, 120.0, 128.0, 0.36)
secondarySystemFill
#78788051rgba(120.0, 120.0, 128.0, 0.32)
tertiarySystemFill
#7676803drgba(118.0, 118.0, 128.0, 0.24)
quaternarySystemFill
#7676802drgba(118.0, 118.0, 128.0, 0.18)
placeholderText
#ebebf54crgba(235.0, 235.0, 245.0, 0.3)
systemBackground
#000000ffrgba(0.0, 0.0, 0.0, 1.0)
secondarySystemBackground
#1c1c1effrgba(28.0, 28.0, 30.0, 1.0)
tertiarySystemBackground
#2c2c2effrgba(44.0, 44.0, 46.0, 1.0)
systemGroupedBackground
#000000ffrgba(0.0, 0.0, 0.0, 1.0)
secondarySystemGroupedBackground
#1c1c1effrgba(28.0, 28.0, 30.0, 1.0)
tertiarySystemGroupedBackground
#2c2c2effrgba(44.0, 44.0, 46.0, 1.0)
separator
#54545899rgba(84.0, 84.0, 88.0, 0.6)
opaqueSeparator
#38383affrgba(56.0, 56.0, 58.0, 1.0)
link
#0984ffffrgba(9.0, 132.0, 255.0, 1.0)
darkText
#000000ffrgba(0.0, 0.0, 0.0, 1.0)
lightText
#ffffff99rgba(255.0, 255.0, 255.0, 0.6)
systemBlue
#0a84ffffrgba(10.0, 132.0, 255.0, 1.0)
systemBrown
#ac8e68ffrgba(172.0, 142.0, 104.0, 1.0)
systemGreen
#30d158ffrgba(48.0, 209.0, 88.0, 1.0)
systemIndigo
#5856d6ffrgba(88.0, 86.0, 214.0, 1.0)
systemOrange
#ff9f0affrgba(255.0, 159.0, 10.0, 1.0)
systemPink
#ff375fffrgba(255.0, 55.0, 95.0, 1.0)
systemPurple
#bf5af2ffrgba(191.0, 90.0, 242.0, 1.0)
systemRed
#ff453affrgba(255.0, 69.0, 58.0, 1.0)
systemTeal
#5ac8faffrgba(90.0, 200.0, 250.0, 1.0)
systemYellow
#ffd60affrgba(255.0, 214.0, 10.0, 1.0)
systemGray
#8e8e93ffrgba(142.0, 142.0, 147.0, 1.0)
systemGray2
#636366ffrgba(99.0, 99.0, 102.0, 1.0)
systemGray3
#48484affrgba(72.0, 72.0, 74.0, 1.0)
systemGray4
#3a3a3cffrgba(58.0, 58.0, 60.0, 1.0)
systemGray5
#2c2c2effrgba(44.0, 44.0, 46.0, 1.0)
systemGray6
#1c1c1effrgba(28.0, 28.0, 30.0, 1.0)

ColorCompatibility full code

Here's the full, generated implementation of ColorCompatibility - this will be used by CIFilter.io when iOS 13 is out of beta.

enum ColorCompatibility {
    static var label: UIColor {
        if #available(iOS 13, *) {
            return .label
        }
        return UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
    }
    static var secondaryLabel: UIColor {
        if #available(iOS 13, *) {
            return .secondaryLabel
        }
        return UIColor(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.2627450980392157, alpha: 0.6)
    }
    static var tertiaryLabel: UIColor {
        if #available(iOS 13, *) {
            return .tertiaryLabel
        }
        return UIColor(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.2627450980392157, alpha: 0.3)
    }
    static var quaternaryLabel: UIColor {
        if #available(iOS 13, *) {
            return .quaternaryLabel
        }
        return UIColor(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.2627450980392157, alpha: 0.18)
    }
    static var systemFill: UIColor {
        if #available(iOS 13, *) {
            return .systemFill
        }
        return UIColor(red: 0.47058823529411764, green: 0.47058823529411764, blue: 0.5019607843137255, alpha: 0.2)
    }
    static var secondarySystemFill: UIColor {
        if #available(iOS 13, *) {
            return .secondarySystemFill
        }
        return UIColor(red: 0.47058823529411764, green: 0.47058823529411764, blue: 0.5019607843137255, alpha: 0.16)
    }
    static var tertiarySystemFill: UIColor {
        if #available(iOS 13, *) {
            return .tertiarySystemFill
        }
        return UIColor(red: 0.4627450980392157, green: 0.4627450980392157, blue: 0.5019607843137255, alpha: 0.12)
    }
    static var quaternarySystemFill: UIColor {
        if #available(iOS 13, *) {
            return .quaternarySystemFill
        }
        return UIColor(red: 0.4549019607843137, green: 0.4549019607843137, blue: 0.5019607843137255, alpha: 0.08)
    }
    static var placeholderText: UIColor {
        if #available(iOS 13, *) {
            return .placeholderText
        }
        return UIColor(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.2627450980392157, alpha: 0.3)
    }
    static var systemBackground: UIColor {
        if #available(iOS 13, *) {
            return .systemBackground
        }
        return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
    }
    static var secondarySystemBackground: UIColor {
        if #available(iOS 13, *) {
            return .secondarySystemBackground
        }
        return UIColor(red: 0.9490196078431372, green: 0.9490196078431372, blue: 0.9686274509803922, alpha: 1.0)
    }
    static var tertiarySystemBackground: UIColor {
        if #available(iOS 13, *) {
            return .tertiarySystemBackground
        }
        return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
    }
    static var systemGroupedBackground: UIColor {
        if #available(iOS 13, *) {
            return .systemGroupedBackground
        }
        return UIColor(red: 0.9490196078431372, green: 0.9490196078431372, blue: 0.9686274509803922, alpha: 1.0)
    }
    static var secondarySystemGroupedBackground: UIColor {
        if #available(iOS 13, *) {
            return .secondarySystemGroupedBackground
        }
        return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
    }
    static var tertiarySystemGroupedBackground: UIColor {
        if #available(iOS 13, *) {
            return .tertiarySystemGroupedBackground
        }
        return UIColor(red: 0.9490196078431372, green: 0.9490196078431372, blue: 0.9686274509803922, alpha: 1.0)
    }
    static var separator: UIColor {
        if #available(iOS 13, *) {
            return .separator
        }
        return UIColor(red: 0.23529411764705882, green: 0.23529411764705882, blue: 0.2627450980392157, alpha: 0.29)
    }
    static var opaqueSeparator: UIColor {
        if #available(iOS 13, *) {
            return .opaqueSeparator
        }
        return UIColor(red: 0.7764705882352941, green: 0.7764705882352941, blue: 0.7843137254901961, alpha: 1.0)
    }
    static var link: UIColor {
        if #available(iOS 13, *) {
            return .link
        }
        return UIColor(red: 0.0, green: 0.47843137254901963, blue: 1.0, alpha: 1.0)
    }
    static var darkText: UIColor {
        if #available(iOS 13, *) {
            return .darkText
        }
        return UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
    }
    static var lightText: UIColor {
        if #available(iOS 13, *) {
            return .lightText
        }
        return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.6)
    }
    static var systemBlue: UIColor {
        if #available(iOS 13, *) {
            return .systemBlue
        }
        return UIColor(red: 0.0, green: 0.47843137254901963, blue: 1.0, alpha: 1.0)
    }
    static var systemBrown: UIColor {
        if #available(iOS 13, *) {
            return .systemBrown
        }
        return UIColor(red: 0.6352941176470588, green: 0.5176470588235295, blue: 0.3686274509803922, alpha: 1.0)
    }
    static var systemGreen: UIColor {
        if #available(iOS 13, *) {
            return .systemGreen
        }
        return UIColor(red: 0.20392156862745098, green: 0.7803921568627451, blue: 0.34901960784313724, alpha: 1.0)
    }
    static var systemIndigo: UIColor {
        if #available(iOS 13, *) {
            return .systemIndigo
        }
        return UIColor(red: 0.34509803921568627, green: 0.33725490196078434, blue: 0.8392156862745098, alpha: 1.0)
    }
    static var systemOrange: UIColor {
        if #available(iOS 13, *) {
            return .systemOrange
        }
        return UIColor(red: 1.0, green: 0.5843137254901961, blue: 0.0, alpha: 1.0)
    }
    static var systemPink: UIColor {
        if #available(iOS 13, *) {
            return .systemPink
        }
        return UIColor(red: 1.0, green: 0.17647058823529413, blue: 0.3333333333333333, alpha: 1.0)
    }
    static var systemPurple: UIColor {
        if #available(iOS 13, *) {
            return .systemPurple
        }
        return UIColor(red: 0.6862745098039216, green: 0.3215686274509804, blue: 0.8705882352941177, alpha: 1.0)
    }
    static var systemRed: UIColor {
        if #available(iOS 13, *) {
            return .systemRed
        }
        return UIColor(red: 1.0, green: 0.23137254901960785, blue: 0.18823529411764706, alpha: 1.0)
    }
    static var systemTeal: UIColor {
        if #available(iOS 13, *) {
            return .systemTeal
        }
        return UIColor(red: 0.35294117647058826, green: 0.7843137254901961, blue: 0.9803921568627451, alpha: 1.0)
    }
    static var systemYellow: UIColor {
        if #available(iOS 13, *) {
            return .systemYellow
        }
        return UIColor(red: 1.0, green: 0.8, blue: 0.0, alpha: 1.0)
    }
    static var systemGray: UIColor {
        if #available(iOS 13, *) {
            return .systemGray
        }
        return UIColor(red: 0.5568627450980392, green: 0.5568627450980392, blue: 0.5764705882352941, alpha: 1.0)
    }
    static var systemGray2: UIColor {
        if #available(iOS 13, *) {
            return .systemGray2
        }
        return UIColor(red: 0.6823529411764706, green: 0.6823529411764706, blue: 0.6980392156862745, alpha: 1.0)
    }
    static var systemGray3: UIColor {
        if #available(iOS 13, *) {
            return .systemGray3
        }
        return UIColor(red: 0.7803921568627451, green: 0.7803921568627451, blue: 0.8, alpha: 1.0)
    }
    static var systemGray4: UIColor {
        if #available(iOS 13, *) {
            return .systemGray4
        }
        return UIColor(red: 0.8196078431372549, green: 0.8196078431372549, blue: 0.8392156862745098, alpha: 1.0)
    }
    static var systemGray5: UIColor {
        if #available(iOS 13, *) {
            return .systemGray5
        }
        return UIColor(red: 0.8980392156862745, green: 0.8980392156862745, blue: 0.9176470588235294, alpha: 1.0)
    }
    static var systemGray6: UIColor {
        if #available(iOS 13, *) {
            return .systemGray6
        }
        return UIColor(red: 0.9490196078431372, green: 0.9490196078431372, blue: 0.9686274509803922, alpha: 1.0)
    }
}