SECRET OF CSS

Exploring the UIColor and Color APIs in iOS | by Riccardo Cipolleschi | Aug, 2022


How to get the most from your colours

0*6gGUnNRlp DsCTqA
Photo by Robert Katzki on Unsplash

We all want to ship high-quality products, and we want to ship them as fast as we can. We like to focus on complex problems, moving fast over pieces of code we have written repeatedly.

However, as the saying goes, the devil is in the details. The quality of a product lies exactly in those details: well-thought animations, for example, or the right colour for the right button.

Today I would like to explore a little more about the UIColor and the Color APIs provided by Apple and share a few useful tricks to empower your app.

Apple released the first device supporting the P3 Display with the iPhone 7. P3 Displays support a wider set of colours than the standard displays: 25% wider. The net effect is that you can write more expressive apps with brighter colours.

The two images below show the difference between a set of standard RGB colours (the left column) and the same colours using P3 Display APIs (the right column). The left simulator is set up with a light theme, while the right simulator uses a dark theme.

1*o bQJCJbq2KVW4gdzY8brw
1*QmhtVv8mw8H9 6tDPrqarQ

You must have a monitor that supports P3 display to see the difference!

You can see how the colours in the left columns look duller than on the right. The difference between the two is more or less evident, depending on the theme.

The API to create Display P3 has been around since iOS 10, so your project likely supports it.

To create a UIKit colour in the extended colour space (sometimes also called Wide Gamut) you use this UIColor init:

UIColor.init(displayP3Red:green:blue:alpha:)

The four values must be from 0 to 1, exactly like the standard UIColor.init(red:green:blue:alpha:) initializer.

SwiftUI’s Color has its initializer to create it using the Display P3 color space:

init(_:red:green:blue:opacity:)

The colour space is identified by the first anonymous parameter. Its default is sRGB, which represents the standard RGB colour space. However, the Color.RGBColorSpace enum also contains the displayP3 value that is exactly what you need.

RGB is not the only representation available to describe a colour. Colours can also be described with a model that uses hue, saturation, and brightness (HSB). Apple provides an init also for this representation:

// UIKit
init(hue:saturation:brightness:alpha:)
// SwiftUI
init(hue:saturation:brightness:opacity:)

These initializers put less emphasis on the tint of the color but let you play more easily with its saturation and brightness. The HSB model is represented by the following cylinder:

1*LUE6eE9o771jailh9Uwr6w
The HSB cylinder.
  • The hue parameter goes from 0 to 1, representing the colour tint. The values are mapped to the cylinder’s degrees from 0° to 360°.
  • The saturation parameter goes from 0 to 1 and represents how much tint you want in your colour. A value of 0 represents grey.
  • The brightness parameter represents how bright the colour is. A value of 0 ends up in black colour.

The HSB representation is automatically rendered using the extended colour range of the P3 Displays for the apps linked against iOS 10 or newer, so you get the brighter colours for free.

By observing some default colours that adapt to the current themes, you may observe that the tint of the colour stays the same, while the colour changes in saturation and brightness when moving from the light to the dark theme.

You can leverage the HSB model to automatically create good colours for the light and dark themes without manually specifying two different colours.

As an experiment, you can write a little script to check how Apple changes these values. The script can be something like this:

You can see in the comments the various operations:

  1. The script defines a UITraitCollection for the light and dark theme.
  2. It defines a set of colours to explore.
  3. For each color, it obtains the light and dark values.
  4. It gets the HSB values.
  5. It prints those values.

The hsba property at lines 17 and 18 is a custom property I prepared that returns the HSBA representation of the colour. The code for that property is the following:

The code is explained in the comment, but, in short, it defines a helper HSBAColor struct to collect the values and to easily print them in the console.

The hsba property just uses the utility getHue(_:saturation:brightness:alpha:) function, provided by UIColor, to extract the values we need.

The result of the execution is the following:

1*Aji4ZEy9YWiWeRWe5UtMnA

You can observe that:

  • The hue for system colour stays almost the same.
  • In most cases, the saturation decreases by a factor ~5–10%.
  • In most cases, the brightness increases by a factor ~5–10%.

Therefore, you can deduct that a dark colour can be described as:

hue(dark) = hue(light)
saturation(dark) = saturation(light)-0.07
brightness(dark) = brightness(light)+0.07

Warning
If you work with a designer that provides values for both the light and dark theme, use them and don’t use this heuristic.

This is good to develop an app or a prototype that supports both themes, without investing a huge amount of time in designing the perfect colour palette.

UI designers have experience in picking the proper colours and the result would be much better than any combination you can generate automatically.

Now, you have to create your dynamic colours. Apple colours are of a special DynamicSystemColor type that can’t be instantiated in client code. There are plenty of articles online that describe how to create dynamic colours, and they all resolve to use this constructor:

init(dynamicProvider:)

This initializer takes a closure in the form of (UITraitCollection) -> UIColor: you can return a colour based on the UITraitCollection.userInterfaceStyle property, which tells you the current app theme.

You can also apply the heuristic from the previous section in this initializer:

This snippet declares a static function to create a dynamic colour, starting from a UIColor. First, it extracts the hsba struct. Then, it uses this struct to create a dark representation of the initial colour. Finally, it returns the color with the dynamicProvider initializer.

You can use it in this way:

UIColor.dynamicColor(UIColor(displayP3Red: 0.75, green:0.25, blue: 0.33, alpha: 1)

If you want a nicer API for that, something that resembles the Apple approach, you can create a static property as an extension of UIColor that returns the dynamicColor. You can also bring that to SwiftUI with another property if you need to:

// Extending UIColor with dynamic properties...
extension UIColor {
static var myColor = UIColor.dynamicColor(UIColor(displayP3Red: 0.75, green:0.25, blue: 0.33, alpha: 1)
}// ...and porting them to SwiftUI
extension Color {
static var myColor = Color(uiColor: .myColor)
}

Today we explored several aspects of iOS’ colours:

  • The difference between standard RGB and Display P3 colour spaces.
  • The difference between the RGB and the hue, saturation, and brightness (HSB) representation.
  • How to automatically create a dark colour from a light one.
  • How to create dynamic colours to responds to light and dark themes.

My hope with this article is to inspire you to take some time to explore Apple’s API, even the basic ones: sometimes, we can get huge wins and make a better product by using the proper API.

And remember, use the DisplayP3 version of your colour instead of the plain RGB one!



News Credit

%d bloggers like this: