# ChromaColorPicker: an HSB color picker for iOS, with handles

> ChromaColorPicker is a Swift view for picking a colour, and the thing that distinguishes it from a stock control is that it holds more than one selection handle at a time. A palette in a design tool is not one colour, it is several, and a control that can only express one is a control you have to work around.

**joncardasis/ChromaColorPicker** — :art: An intuitive iOS color picker built in Swift.

- Repository: https://github.com/joncardasis/ChromaColorPicker
- Stars: 571 · Forks: 127
- Language: Swift
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/joncardasis-chromacolorpicker

## What the library is

ChromaColorPicker is an intuitive HSB color picker built in Swift, supporting multiple selection handles and customizable to your needs.

That is the whole pitch, and the second half of it is the interesting half. A colour picker is normally a control with one value: you drag it, it holds a colour, and that is the answer. ChromaColorPicker holds a set of handles on a two dimensional hue and saturation field, and you can have as many as you need. A design tool that lets a user pick three colours at once and shows them as three dots on one field is a different thing from a colour well, and this is the former.

The library is an iOS view written in Swift, with the version badge at five, and it is distributed three ways: through Carthage, through CocoaPods, and manually by adding the files from the source folder to a project. The readme also shows a package manifest file in the repository root, so the Swift Package Manager is a fourth route in practice even though the readme's installation section does not list it separately.

The versioning history is worth a paragraph, because the readme puts it in a collapsible section and the details matter for anybody arriving fresh. Version one is on a separate legacy branch, its pod is still published but is deprecated, and projects are told to migrate to the second major version. So the library has a deliberate break: the first version is frozen and available, the second is the one to build on, and the migration is a decision the maintainer made rather than something that happened by accident.

The release history shows the second major version arriving in the middle of 2020, with patch releases after it, and the last push is considerably later. That combination, a mature release line and a recent commit, describes a library that is in maintenance rather than in active development, and the last push is more likely to be a compatibility fix than a new feature.

The licence is MIT, and the project is published on a package repository with a download count badge, which is the standard way to judge whether a library of this kind is worth adopting.

## The two components and the connection between them

The component table lists two things, and the second one is what turns this from a single view into a small system.

The picker itself is an HSB colour picker with support for adding multiple colour selection handles. The second is a brightness slider, and the description is precise about what it is: a slider control that can be attached to any picker through a connect method, and that can also function as a stand-alone control.

The two together are the reason to use this library rather than drawing your own. HSB has three components, and a field of hue and saturation only covers two of them, so the brightness needs somewhere to live. Most implementations either bolt a third slider onto the side or try to cram brightness into the same gesture space, and both approaches have a cost. A separate control is a control, and it can be placed wherever the layout wants it.

The connection method is available from either side, which the readme shows with an inline comment: you can call it on the picker with the slider as the argument, or on the slider with the picker. That is a small ergonomic decision, and it matters in practice because the direction you call it in depends on which object you happen to hold when the view is being set up, and forcing one direction means a stored property or an awkward conditional at every call site.

The example code makes the whole arrangement concrete.

```Swift
let colorPicker = ChromaColorPicker(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
addSubview(colorPicker)

// Optional: Attach a ChromaBrightnessSlider to a ChromaColorPicker
let brightnessSlider = ChromaBrightnessSlider(frame: CGRect(x: 0, y: 0, width: 280, height: 32))
addSubview(brightnessSlider)

colorPicker.connect(brightnessSlider) // or `brightnessSlider.connect(to: colorPicker)`
```

Note the word optional on the brightness slider. The picker is usable on its own, which matters because not every design has room for a second control, and the slider being usable on its own matters for the opposite reason: there are layouts where you want a brightness control for a value that is not a colour, or where the colour is shown elsewhere.

Both are plain views created with a frame and added as subviews, so nothing here depends on a storyboard, a container, or any particular way of laying out the interface.

## Three ways to add a handle

The multiple handles feature is exposed through one method with three forms, and the differences between them are about how much control you get.

```Swift
// Add handle at color
let peachColor = UIColor(red: 1, green: 203 / 255, blue: 164 / 255, alpha: 1)
colorPicker.addHandle(at: peachColor)

// Add handle with reference
let customHandle = ChromaColorHandle()
customHandle.color = UIColor.purple
colorPicker.addHandle(customHandle)

// Add handle and keep reference
let handle = colorPicker.addHandle(at: .blue)
```

The first form places a handle at a colour you already have, which is the case where the user has picked a colour elsewhere, or the application has a default, or you are restoring a saved palette. The colour is constructed with the classic initialiser, and the readme's example divides the green and blue channels by two hundred and fifty-five, which is the older way of writing those fractions and a reminder of how long this library has been around.

The second form is the one that gives you full control, because you construct the handle yourself, set its colour, and hand it over. That is the hook that the custom icon feature builds on.

The third form is the convenience: you pass a colour and the method returns the handle it created, so you can hold on to it and change it later. That matters more than it looks. A handle is a thing with a position on the field and a colour, and an application that wants to update a palette entry, or to animate a handle to a new place, or to remove one, needs a reference to it. Returning it from the call that creates it is what makes that possible without a lookup.

So the three forms cover three levels of involvement: place one at a colour, bring your own, or take the one that was made and keep it. The readme also points at the example application for more, and the repository does contain an example project with a view controller, which is the place to see the handles being added and moved in practice.

## Putting your own icon on a handle

The custom handle icon feature is the one that decides whether a colour picker looks like it belongs in your application, and it works by attaching an accessory view to the handle rather than by subclassing anything.

```Swift
let homeHandle = ChomaColorHandle(color: .blue)
let imageView = UIImageView(image: #imageLiteral(resourceName: "home-icon").withRenderingMode(.alwaysTemplate))
imageView.contentMode = .scaleAspectFit
imageView.tintColor = .white
homeHandle.accessoryView = imageView
homeHandle.accessoryViewEdgeInsets = UIEdgeInsets(top: 2, left: 4, bottom: 4, right: 4)

colorPicker.addHandle(homeHandle)
```

Three details carry the whole technique. The image is loaded as a resource literal, which is the modern way of referring to an image in source, so the file is found by the build system rather than by a string that can be wrong. The rendering mode is set to always template, which tells the system to draw the image as a mask and colour it with the view's tint, rather than using the image's own colours. And the tint colour is then set, so the icon takes the colour you want rather than whatever was in the file.

That combination is the standard way to make an icon that adapts to a theme, and it means you do not need a separate asset for every colour. The content mode is set to scale to fit, so the icon does not distort, and the edge insets position the accessory view within the handle, which is the small amount of layout that a custom accessory needs.

Two things about this snippet are worth flagging to a reader rather than leaving them to be discovered. The edge insets are asymmetric, with a larger left inset than right, which is a hint that the default handle has some shape to it and the accessory sits inside it. And the type name in the first line is spelled without the second letter, which is a typo in the readme rather than in the library; a reader copying the snippet verbatim will not compile it.

Both are the sort of thing a documentation snippet carries when it was written quickly, and the fact that the library is old enough to have been documented this way is the same fact that explains its other quirks.

## Control events, and what is in the repository

Both components conform to the standard control protocol, and the readme is precise about which events each one sends, because that is what you need in order to write the code that reacts to a colour changing.

The picker sends a value changed event whenever the colour has changed, and a touch up event when a handle is released. The slider sends a touch down event when it is grabbed, a value changed event when it is moved and the value has changed, and a touch up event when the handle is released.

The two tables are similar but not identical, and the difference is meaningful. The picker's value changed fires while a handle is being dragged, whereas the slider's fires when it is moved and the value has changed, which is the same idea stated more carefully. The picker's touch up fires when a handle is released, and the slider's touch down fires when it is grabbed, so a pair of sliders would give you a complete gesture lifecycle while a picker only tells you about the end of one.

The observation mechanism is the ordinary one, with a target and an action.

```Swift
// Example
brightnessSlider.addTarget(self, action: #selector(sliderDidValueChange(_:)), for: .valueChanged)

@objc func sliderDidValueChange(_ slider: ChromaBrightnessSlider) {
  print("new color: \(slider.currentColor)")
}
```

And the action reads the slider's current colour property, which is worth noticing: the slider carries a colour, not a brightness value. Since it can stand alone as a control, that colour is a complete value in its own right, and an application that wants brightness alone has to derive it.

The repository layout matches a Swift package with an example project. There is a source directory that holds the library, a tests directory, an example project with its own app delegate, view controller, asset catalogue and info file, and the Xcode project for the library itself. There is a pod specification, a package manifest, a Ruby gemfile with a lock file, which is the convention for a project that uses a fastlane lane for its releases and screenshots, and a directory for those lanes. The continuous integration configuration is from the service that has since been folded into another, which places the project in time.

And the badge row says what the project supports: Swift five, iOS, MIT, CocoaPods and Carthage, with a build status badge. A library that still builds on a continuous integration service from that era, with a Swift five badge and a pod for the old major version, is one you should try in a branch before you depend on it.

## Conclusion

ChromaColorPicker fits an iOS team that needs a colour control that can express a palette rather than a single colour, and that wants to put its own icon on the handles. It does not fit an application that only needs a standard colour well, because the system offers one and this does not add to it. Verify first which version you install, since the first major version lives on a separate branch with its own pod and is deprecated, that the brightness slider is connected in the direction you want, since the connection method is available from either side, and that the release you pick matches the Swift version and the deployment target your application uses.

## FAQ

### What does ChromaColorPicker do?

It is an HSB colour picker for iOS written in Swift that supports multiple selection handles, so a user can hold several colours on one field rather than one at a time. It ships with a companion brightness slider that can be connected to a picker or used on its own, and the handles can carry custom icons.

### How do I add a colour handle to ChromaColorPicker?

There are three ways. Pass a colour to add a handle at that colour, construct a handle yourself, set its colour and pass that in, or call the method and keep the handle it returns. The last form is what lets you update or remove a handle later, since you need a reference to it.

### How do I give a ChromaColorPicker handle a custom icon?

Create the handle, attach an image view as its accessory view, set the image's rendering mode to always template so it takes the view's tint colour, set the tint colour, and give the handle accessory view edge insets to position it. The handle is then added to the picker in the usual way.

### How does the ChromaColorPicker brightness slider connect to the picker?

Through a connect method that can be called from either side, so you can call it on the picker with the slider as the argument or on the slider with the picker. The connection is optional, and the slider also works as a standalone control on its own.

### How do I install ChromaColorPicker?

Through Carthage with the github line, through CocoaPods with the pod line, by adding the files from the source folder to your project by hand, or with the Swift Package Manager using the manifest in the repository. The first major version lives on a separate legacy branch, its pod is deprecated, and projects are told to migrate to the second major version.

## Sources

- [Issues](https://github.com/joncardasis/ChromaColorPicker/issues)
- [joncardasis/ChromaColorPicker on GitHub](https://github.com/joncardasis/ChromaColorPicker)
- [License: MIT](https://github.com/joncardasis/ChromaColorPicker/blob/master/LICENSE)
- [README](https://github.com/joncardasis/ChromaColorPicker/blob/master/README.md)
- [Releases](https://github.com/joncardasis/ChromaColorPicker/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/joncardasis-chromacolorpicker
