ChromaTouch: A 3D Touch Color Picker in Swift

DZone 's Guide to

ChromaTouch: A 3D Touch Color Picker in Swift

Get to grips with implementing 3D Touch in your apps with this simple project.

· Mobile Zone ·
Free Resource

If you're fairly new to Swift, you may have found my last post on 3D Touch a little daunting. Here's a much smaller project that may be a little easier to follow to get up and running with force, peek, pop and preview actions. 

ChromaTouch is an HSL-based color picker where the user can set the color with three horizontal sliders or by touching over a colur swatch where horizontal movement sets hue, vertical set saturation and the force of the touch sets the lightness of the color. As the user moves their finger over the swatch, the sliders update to reflect the new HSL values.

By force-touching the sliders, the user is presented with a small preview displaying the RGB hex value of their color:

By swiping up they can set their colour to one of three presets:

And by deep pressing, they're presented with a full screen preview of their color which is dismissed with a tap:

Let's look at each part of the 3D Touch code to see how everything has been implemented.

Setting Lightness with Force 

My Swatch class is responsible for handling the user's touches in three dimensions and populating a tuple containing the three values for hue, saturation and lightness. Each touch returned in touchesMoved() contains two dimensional spatial data in its touchLocation property and the amount of force the user is exerting in its force property. 

We can normalize these values to between zero and one by dividing the positions by the bounds of the Swatch's view and the force by the maximumPossibleForce. With those normalized values, we can construct an object representing the three properties of our desired color:


override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
        super.touchesMoved(touches, withEvent: event)

        guard let touch = touches.first else

        let touchLocation = touch.locationInView(self)
        let force = touch.force
        let maximumPossibleForce = touch.maximumPossibleForce

        let normalisedXPosition = touchLocation.x / frame.width
        let normalisedYPosition = touchLocation.y / frame.height
        let normalisedZPosition = force / maximumPossibleForce

        hsl = HSL(hue: normalisedXPosition,
            saturation: normalisedYPosition,
            lightness: normalisedZPosition)

If you look at my code, you'll notice I keep a variable holding the previous touch force and compare it against the current touch force. This allows me to ignore large differences which happen when the user lifts their finger to end the gesture.


Peeking happens when the user presses down on the sliders. In my main view controller, as I create each of the three sliders, I register them for previewing with that main view controller:

for sliderWidget in [hueSlider, saturationSlider, lightnessSlider]
        sliderWidget.addTarget(self, action: "sliderChangeHandler", forControlEvents: UIControlEvents.ValueChanged)

        registerForPreviewingWithDelegate(self, sourceView: sliderWidget)

My view controller needs to implement UIViewControllerPreviewingDelegate in order to tell iOS what to pop up when the user wishes to preview. In the case of ChromaTouch, it's a PeekViewController and it's defined in previewingContext(viewControllerForLocation):

   func previewingContext(previewingContext: UIViewControllerPreviewing,
        viewControllerForLocation location: CGPoint) -> UIViewController?
        let peek = PeekViewController(hsl: hsl,
            delegate: previewingContext.delegate)

        return peek

PeekViewController is pretty basic, containing a UILabel that has its text set based on an extension I wrote to extract the RGB components from a UIColor.

Preview Actions

By swiping up on the preview, the user can set their colour to a preset. This is super simple to implement: all my PeekViewController needs to do is return an array of UIPreviewActionItem which I create based on an array of predefined colour enumerations:

var previewActions: [UIPreviewActionItem]
        return [ColorPresets.Red, ColorPresets.Green, ColorPresets.Blue].map
            UIPreviewAction(title: $0.rawValue,
                style: UIPreviewActionStyle.Default,
                    (previewAction, viewController) in
                    (viewController as? PeekViewController)?.updateColor(previewAction)

Because I passed the main view controller in the constructor of PeekViewController as delegate, theupdateColor() method in the PeekViewController can pass a newly constructed HSL tuple to it based on the colour selected as a preview action:

func updateColor(previewAction: UIPreviewActionItem)
        guard let delegate = delegate as? ChromaTouchViewController,
            preset = ColorPresets(rawValue: previewAction.title) else

        let hue: CGFloat

        switch preset
        case .Blue:
            hue = 0.667
        case .Green:
            hue = 0.333
        case .Red:
            hue = 0

        delegate.hsl = HSL(hue: hue, saturation: 1, lightness: 1)


The final step is popping: when the user presses deeply on the preview, the preview will vanish (this is managed by the framework) and the main view controller is shown again. However, here I want to hide the sliders and make the swatch full screen. Once the user taps, I want the screen to return to its default state.

Popping requires the second method from UIViewControllerPreviewingDelegatepreviewingContext(commitViewController). Here, I simply turn off user interaction on the swatch and hide the stack view containing the three sliders:

 func previewingContext(previewingContext: UIViewControllerPreviewing,
        commitViewController viewControllerToCommit: UIViewController)
        swatch.userInteractionEnabled = false
        progressBarsStackView.hidden = true

To respond to the tap to return the user interface back to default, ChromaTouchViewController reenables user interaction on the swatch and unhides the progress bars:

 override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
        super.touchesBegan(touches, withEvent: event)

        swatch.userInteractionEnabled = true

        UIView.animateWithDuration(0.25){ self.progressBarsStackView.hidden = false }


I'm beginning to really love peek and pop in the day-to-day iOS apps. As I mentioned in my previous post and as hopefully demonstrated here, it's super easy to implement.

All the code for this project is available in my GitHub repository here. Enjoy!

Just For Fun...

Finally, I couldn't resist taking my old Core Image filtering live video project and having a CIBumpFilter controlled by 3D Touch. Here, the force controls the bump filter's scale. The branch for this silliness is right here.

swift ,ios ,3d touch

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}