Skip to content

Building a custom ring

A pet project of mine requires replicating one of the progress indicators in the Health app. I want to be able to configure the ring’s thickness, colour, and percentage of completion.

Before launching Xcode and start writing code right away, I wrote down what I knew about the problem I wanted to solve:

  • CAShapeLayer seems to be a good fit for this.
  • Animatable properties are, well, animatable. And for free. All you need to do is set their new value, and core animation will take care of animating the transition for you.
  • It is easy to build an oval UIBezierPath.
  • It is easy to apply transformations to bezier paths
  • It should be possible to configure the view both programatically and in Interface Builder.

With that in mind, I decided to build a subclass of UIView, insert a CAShapeLayer into it, and bind the “progress” property of my view to the layer’s strokeEnd property. Also, I decided to took this as an opportunity to explore @IBDesignable and @IBInspectable, which is something I’d never done before.

And this is the end result:

animation-small

The source code is available on GitHub, but let’s take a look at some of the highlights.

Display a custom view in Interface Builder

This one was easier than I expected. I can not claim to be an expert in Interface Builder, so I expected a lot of friction here, but annotating the class as IBInspectable was enough to make Interface Builder aware of it:

Making the view configurable through interface Builder (as well as programatically) was also quite easy, just by annotating properties as IBInspectable and providing a default value for them:

Building the actual ring

There might be a better way to do this -I’m looking at you, UIBezierPath(arcCenter, radius, startAngle, endAngle,clockwise)- but I decided to rely on the fact that UIBezierPath’s strokeEnd is animatable (which means that everytime I cahnge its value, CoreAnimation will animate the changes for me), so I built the path as an oval, and set the strokeEnd to the value of the progress property (which is normalized between 0 and 1).

Of course, that makes the path start at the X axis, so the path’s layer needs to be rotated -PI/2.

After that, it is just a matter of setting the proper frame, and path in the layoutSubviews method. Here is the relevant code:

The full source is available on GitHub, and can be pulled in as a dependency through Carthage.

One Comment

  1. […] back to the point of this post. Remember the Ring we discussed in the previous post? If you don’t, well, it’s basically a UIView subclass, that exposes a few public […]

Leave a Reply

Your email address will not be published. Required fields are marked *