Presenting a Color Picker in SwiftUI

When making apps, it's sometimes necessary to provide users with a specific functionality; to select and change the color of one or more visual elements of the app. Think, for example, of a text editing app. Users might want to change both the background and the text color according to their own personal preferences and taste.

Implementing a custom palette is easy if color collection should contain a limited set of colors. However, if we want to give users advanced options, then implementing such a custom control can get quite complicated.

Thankfully, SwiftUI contains a built-in view just for that, available in iOS 14 and above. It's a system provided color picker both in iOS and macOS; and in this post I'm going to show how to integrate it in your apps.

Using the color picker on iOS

In its simplest form, here is what it takes to present a color picker in SwiftUI:

ColorPicker("Choose a background color", selection: $bgColor)

There are two arguments to provide when initializing a ColorPicker view; a text that will be displayed as the picker's label, and a binding value, usually of a @State property.

Suppose that the above line presents the color picker in order to let users change the background color of a view. This single line initializes and presents the color picker, but it's not enough as it is now. According to Apple documentation, we must include the color picker in a container view; a VStack, HStack, List, and so on.

To demonstrate that, as well as to show a more complete example, take a look at the following code snippet:

struct ContentView: View {
    @State private var bgColor = Color.blue.opacity(0.5)
    
    var body: some View {
        VStack {
            ColorPicker("Choose a background color", selection: $bgColor)
                .padding(.horizontal)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(bgColor)
        .ignoresSafeArea()        
    }
}

The above presents a VStack that covers the entire view ignoring the safe area, and using the background color stored in the bgColor state property. The VStack contains a ColorPicker instance, which we can use to change the background color.

Color picker with label and color well

See that the text is appearing as label next to the color well that displays the currently selected color. By tapping on it, the following view is showing up:

Presented color picker showing grid view.

This time, the picker shows the text as a top title. Besides that, however, it displays colors in three different ways; the first one is a grid where users can simply tap to select a color. Right below that, there is also a colored slider that allows to change the opacity of the color.

If grid is not a suitable representation, then users can display a color spectrum by selecting the Spectrum tab at the top:

Presented color picker showing color spectrum.

There is a third option to specify a color as well, and lies behind the third tab named Sliders. There, it's possible to set the red, green and blue values of a color in two ways; either by using the respective sliders, or by typing actual values ranging from 0 to 255. In addition, there is one more field, indicating the equivalent hex value of the color, which is also editable.

Even more, we can switch between two color spaces; sRGB and Display P3, by tapping on the button right before the color's hex value.

Presented color picker showing color sliders.

If the above are not enough, color picker also provides an eyedropper button; it allows to pick a color from anywhere in the screen:

Eyedropper from color picker.

Note that the color picker won't be dismissed automatically once users select a color. It's still necessary to tap on the close button on the top-right corner.

Color picker without opacity

As demonstrated in the previous screenshots, the color picker contains a slider in order to control a color's alpha value (opacity). However, it might not be always desirable to allow users to alter the opacity.

For such cases, we can actually hide the opacity slider. All it takes is an additional argument in the picker's initialization:

ColorPicker("Choose a background color",
            selection: $bgColor,
            supportsOpacity: false)

supportsOpacity is true by default , and the opacity slider is visible in the picker. By setting false, color picker doesn't display it:

Presented color picker showing grid without opacity slider.

Multiple color pickers

As mentioned previously, the color picker expects the binding value of a color property as an argument. However, what if it's necessary to pick colors for more than one visual elements?

In such cases, it's perfectly fine to have as many color pickers as necessary in the view. The important thing is to have an equal number of @State properties in order to use their binding values in the pickers.

Let's have a look at another example. The following code has a VStack that contains two shapes with two different foreground colors. Right after these shapes, there is a form representing fake settings with a single section; and inside the section there are two color picker views:

struct ContentView: View {
    @State private var shape1Color = Color.yellow
    @State private var shape2Color = Color.blue
    
    var body: some View {
        VStack {
            HStack {
                RoundedRectangle(cornerRadius: 25)
                    .frame(width: 120, height: 120)
                    .foregroundColor(shape1Color)
                    .padding(.horizontal)
                
                RoundedRectangle(cornerRadius: 25)
                    .frame(width: 120, height: 120)
                    .foregroundColor(shape2Color)
                    .padding(.horizontal)
            }
            
            Form {
                Section(header: Text("Color Settings")) {
                    ColorPicker("Shape 1 color", selection: $shape1Color)
                    ColorPicker("Shape 2 color", selection: $shape2Color)
                }
            }
            .frame(height: 150)
            .padding(.horizontal)
            .padding(.top, 50)
        }
    }
}

See that there are two @State properties this time. Each color picker accepts a different binding value, so it's possible to change both colors. Depending on the picker that will be used, the color of the respective shape is modified.

Two color pickers with labels and color wells.

Alternative color picker labels

In all previous examples a simple String value is what color pickers show as a label. However, there is an alternative initializer that we can use in order to specify a custom label instead:

ColorPicker(selection: $shape2Color, label: {
    Text("Change 🟦 color")
        .font(.title)
        .fontWeight(.bold)
})

In the above example, the picker's label is the last argument, and it's a container view. In its body we can implement a more advanced label using other SwiftUI views.

Note

If the the label is a Text view, then its content will appear as the picker's title above colors. If not, then a default title will be set by the system automatically.

Here's how the picker shows now:

Two color pickers with the second having custom label.

Note that turning off the opacity is also possible with this initializer too. We just have to set false to the supportsOpacity argument, which comes before the label:

ColorPicker(selection: $shape2Color, supportsOpacity: false, label: {
    ...
})

Color picker on macOS

When it comes to macOS, there is practically no difference in the implementation of the color picker. Everything you've read so far applies on macOS too.

What changes is the appearance of the color picker in the app. Instead of a circular, there is a rectangular color well here. When presented, what we see is the familiar system color picker that we all know. And as it was previously shown in iOS, it's possible to hide the opacity slider as well.

To get a taste of how it looks on macOS, an implementation similar to the above one with the two colored shapes, and two color pickers, will give us the following:

Summary

Color picker is a handy tool that we may use without resorting to custom implementations. It's available natively in SwiftUI, and as you've seen in this post, initializing and presenting one takes a single line of code. The actual benefit of using the system color picker is that users meet a familiar control; that makes it easy for them to use it. And that sounds like a big deal in terms of user experience. Thank you for reading, stay tuned for more posts!