In the world of iOS development, there is an ongoing debate on whether using the Storyboard, or Interface Builder, makes writing apps easier, or if it is an additional bother that just adds another layer of complexity. I am on the side of trying to make Interface Builder work for me and reduce the amount of un-needed code in my apps. 

The best line of code you will ever write is the line you don’t have to write.

If you can remove excess code that isn’t giving you much value, you save yourself time and increase the readability of your code. This idea inspired me to explore Apple’s IBDesignables.

IBDesignables enable you to see changes you’ve made to objects in code, then see these changes in the Interface Builder and change attributes of the objects. This is cool because it is much faster to implement changes to an object in code, then check the changes in the Interface Builder, versus building and running the project. Interface Builder still has to build in order to show the changes, but this process is much faster than running the project after each change you make.
Let’s try it out
To see a powerful example of this process, we are going to build a circle in a view that we can see in the Interface Builder. Then we will make changes and watch them update live in the Interface Builder. In the end we want to be able to see something like this:

The basic design of what we will build is a subclass of UIView that we draw a circle around, using UIBezierPath and CAShapeLayer.

First create a new project called RadialProgressView, and select whichever language you want to build this in. I will build it using Swift throughout this tutorial, but I have example projects on GitHub in both [Objective-C](LINK) and [Swift](LINK). 

Next, create a new subclass of UIView, called RDPView. In order to create the UIBezierPath, we need to lay it out in the drawRect method. This is the only place that we are able to create a UIBezierPath, so we will create a helper method to create the path. I will call my method, drawPath(), and the code for it is below.

 private func drawPath() {
let mainCircle = UIBezierPath() //1
let startAngle = CGFloat(M_PI * 1.5) //2
let endAngle = startAngle + CGFloat(M_PI * 2) //3
let center = CGPoint(x: CGRectGetWidth(self.frame)/2,
y: CGRectGetHeight(self.frame)/2)
mainCircle.addArcWithCenter(center,
radius: CGRectGetWidth(self.frame)/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true) //4
}

What are we doing here?

  1. Instantiate a new UIBezierPath
  2. Declare a starting angle, this is where we want to start our path. Since we are drawing a circle, it requires some math to decide where to start it. If you have circle math questions, Math.com has [a great resource](http://www.math.com/tables/geometry/circles.htm).
  3. After deciding where we want to begin drawing our circle, we want to set up where it ends. Again, if the math is confusing, give that Math.com resource a try.
  4. Last thing we need is the center of where the circle will be drawn, since we want the center of the view we are creating, we can just use half of the width, and half of the height of the view. 
  5. Now we are going to make the UIBezierPath into a circle. In order to do this, we will call a method on our newly instantiated UIBezierPath called addArcWithCenter(). We can now use the variables we have set up. This method takes a CGPoint parameter for the center of the arc, a CGFloat parameter for the radius of the arc, a CGFloat to represent the startingAngle for the arc, a CGFloat for the endAngle of the arc, and then a Boolean for whether it will be clockwise. If you are confused by the math, again look at the provided article, it is what I used.

Now that we have created a new UIBezierPath that will be the desired arc according to the size of our RDPView, but we still need to add color to the path in order to see it. In order to accomplish this, we will instantiate a new CAShapeLayer, and add it to our path, as well as the layer of our UIView subclass. I will do this in a helper method as well, and call it drawCircleLayerForPath(path: UIBezierPath). Below is how I setup this method.

private func addLayerForPath(path: UIBezierPath) {
let circleLayer = CAShapeLayer() //1
circleLayer.path = path.CGPath //2
circleLayer.strokeColor = UIColor.redColor().CGColor //3
circleLayer.fillColor = UIColor.clearColor().CGColor //4
circleLayer.lineWidth = 5.0 //5
circleLayer.strokeStart = 0.0 //6
circleLayer.strokeEnd = 1.0 //7
self.layer.addSublayer(circleLayer) //8
}
  1. We need to instantiate a new instance of a CAShapeLayer, in order to add it to our path.
  2. This sets the path that the CAShapeLayer will follow.
  3. Set the color of what we want to layer to be.
  4. Set the fill color to clearColor, so that the circle is not filled in.
  5. Set the width of what we want the circle to be. 
  6. When adding a CAShapeLayer, it fills in the desired CGPath according of a scale of 0.0 to 1.0, where 0.0 is the start of the path, and 1.0 is the end. For our CAShapeLayer, we have to set where we want the layer to start on the path. Since we are filling in the full circle, we will start at 0.0.
  7. Along the same logic as the startPoint, we need to decide where we want the circle to end filling along the path. Let’s set it to 1.0 for now so the full circle will be filled in. 
  8. Finally, we need to add this new layer as a sublayer of the UIView’s layer.

Now we have all of the setup we need in order to see our circle in Interface Builder, minus a few small details. In order to be able to see code changes in Interface Builder, we have to set up our object to be IBDesignable. Since I am writing this in Swift, I will just add @IBDesignable before the class declaration of my object. My class declaration looks like:  

@IBDesignable class RDPView: UIView {

 

Now I need to call my methods in the correct place, so that I can see my circle drawn on the view in the storyboard, I need to call my drawPath() in the drawRect() method of our UIView subclass. We have to call that in the drawRect method, because that is the only place we are allowed to create a UIBezierPath. I will then call my addLayerForPath() method after adding the arc to the UIBezierPath in the drawPath() method.

All that is left is to add a UIView to the UIViewController in the storyboard, and then set its class to be RDPView. If you have called you methods in the right places, then you will see your red circle after setting the class for your UIView in storyboard. It looks cool, but you will notice, that the circle is cut off on the sides. There are several ways to fix this, and it deals with the width of the layer we are drawing. Since I am using storyboard to layout my element, I will use an IBInspectable in order to set the width of my circle, and be able to change it in storyboard. To set this up it looks like:

@IBInspectable var circleWidth: CGFloat!

We can now change this value in the storyboard, using the Attributes Inspector, but since we don’t use it in our code, it does not change anything. In order to make this work, we need to use this value in our code when drawing our circle. 

I will begin with correcting why the circle is cutoff on the edges of my view. This is quite simple, and just requires some additional math when drawing the arc. In the drawPath() I will simply subtract self.circleWidth/2 from the radius of the circle. Lastly in the addLayerForPath() I will change the value for the lineWidth of the CAShapeLayer.  

Now, we should be able to change the value of the circleWidth in the storyboard, and see our changes in real time! Even cooler, we can set up many different IBInspectable properties to watch with Interface Builder. We can see these changes in Interface Builder and when the project is run.

It is important to note that, even though this is called in the drawRect() method, there are other methods which could be more appropriate to use for other UI changes you make to objects. I used drawRect() here because in order to draw a UIBezierPath, it has to be called in drawRect. But for most other changes I would use prepareForInterfaceBuilder(), and then call it as well in awakeFromNib(). The prepareForInterfaceBuilder() will handle the setup for changes you want to see in the Interface Builder, and then the awakeFromNib() is called before the object is shown when the project runs. 

Generally if you are changing element’s background colors, or adding other elements to the object, I would do that setup in the awakeFromNib(), and prepareForInterfaceBuilder(). These methods are better for those type of things, but when dealing with UIBezierPaths, we have to set them up in the drawRect() method.
Some projects you can use
Here are links to two projects where I am building these type of elements that others can easily use. 

Objective-C: github.com/RichFell/RFCircularProgressView
Swift: github.com/RichFell/RFSwiftRadialProgress

If you like the repositories, feel free to pull out the framework and start using the elements. I hope to make them also available via Cocoapods in the near future. I am continuing work on them, so if you feel inclined to fix something, or think of something cool to add, feel free to fork and submit a pull request.