Color Pickers and Combine in iOS 14
In iOS 14, Apple introduced UIColorPickerViewController
as a new system control for selecting colors. The controller—along with its SwiftUI counterpart, ColorPicker
—enables developers to add color selection to their apps without having to build the capability themselves.
In this article, I'll describe how to add a color picker to a view controller and how to integrate it with Combine to improve how we respond to color selections by a user.
Adding the color picker is as straightforward as creating the controller itself and then presenting it from the existing view controller. Once created, we have the option to specify the starting color and whether we want the user to be able to adjust the color’s alpha (i.e. opaqueness) in the picker.
class ColorViewController: UIViewController {
private lazy var colorPicker = makeColorPicker()
...
private func makeColorPicker() -> UIColorPickerViewController {
let vc = UIColorPickerViewController()
vc.delegate = self
vc.selectedColor = .systemPurple
vc.supportsAlpha = false
return vc
}
private func presentColorPicker() {
present(colorPicker, animated: true)
}
}
When instantiating the UIColorPickerViewController
, we’ll also have the opportunity to set its delegate, unsurprisingly named UIColorPickerViewControllerDelegate
. This protocol specifies two optional functions: one that is called when the user presses the picker’s close button and another when the controller’s selectedColor
property changes.
With the delegate set, the code below updates the view’s background when the delegate method is called. Easy enough!
extension ColorViewController: UIColorPickerViewControllerDelegate {
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
view.backgroundColor = viewController.selectedColor
}
}
Although this works, we can integrate Combine into our primary view controller to ensure this code remains performant if we want to complete other operations, such as updating a model or other parts of the UI.
Combine enables one to write a pipeline for transforming values. We’ll use it for setting the view’s background color instead of changing the value directly in the color picker’s delegate.
import UIKit
import Combine
class ColorViewController: UIViewController {
private lazy var colorPicker = makeColorPicker()
private var cancellables = Set<AnyCancellable>()
@Published private var backgroundColor: UIColor = .systemPurple
override func viewDidLoad() {
super.viewDidLoad()
$backgroundColor
.debounce(for: 0.3, scheduler: DispatchQueue.main)
.removeDuplicates()
.sink { [weak self] color in
self?.view.backgroundColor = color
// Other operations, such as updating a model
}.store(in: &cancellables)
}
...
}
extension ColorViewController: UIColorPickerViewControllerDelegate {
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
backgroundColor = viewController.selectedColor
}
}
To do this, there are a few changes:
We first need to import Combine to use the framework.
The view controller has two new properties: an empty set to store our Combine subscription and a
backgroundColor
property with the@Published
property wrapper.In the view controller’s
viewDidLoad
method, we subscribe to changes for thebackgroundColor
property.
By storing our background color in a property with the @Published
property wrapper, we can subscribe to any changes in viewDidLoad
and update our view’s background color whenever it changes. For more on @Published
, I recommend reading John Sundell’s great article on the topic.
In addition, we can use several operators to ensure the performance of our work. First, .debounce
allows us to rate limit changes to ensure we aren’t updating our value if the user is selecting colors in rapid succession (for example, a user could be swiping their finger across the color picker). By setting a threshold of 0.3 seconds, the publisher will only send a value further down the pipeline if a user doesn’t select another color for that amount of time.
The next operator, .removeDuplicates
, does what it says. If a user selects the same color as the existing backgroundColor
, it will stop the pipeline. This saves us from updating the background (or performing other operations) if the color hasn’t changed.
Finally, we subscribe to the pipeline with sink
, which we give a closure that sets the background color to the new value. This is also where we’d perform other actions such as updating a model or other UI elements on the screen.
Using the Combine framework allows Apple platform developers to write powerful Swift code in the style of reactive programming. Although adding color pickers to an app in iOS 14 does not require a huge lift, we can use Combine to improve a basic implementation by baking in useful logic.
Thanks for reading this article. If you’ve found it useful, please let me know or share it with others!