Creating iOS Custom Views in UIKit

I first started working with iOS fresh out of university. Working with an unfamiliar platform and programming language was challenging. What really made me nervous, was the designs coming from the creative team. i know a great design can breathe new life into a website, but unique high concept designs often require the creation of iOS custom views in UIKit.

As a new developer (that was me at the time!), this can be pretty daunting. So the aim of this article is to help any developer convert designs into a functional user-interface elements.

iOS Custom Views

Views are the fundamental building blocks of your app’s user interface. Normally, you create views in your storyboards by dragging them from the library to your canvas. But, sometimes you need to create an element that is not available using the standard ‘label’ or ‘button’ elements in UIKit. This is when you need a custom view.

By the way, if any of the terminology in this article sounds unfamiliar, you might want to check out Apple’s UIKit documentation. From now on, I’ll just assume you have a working knowledge of UIKit and Swift.

The Initial Design

Let’s start by picking a design that cannot be recreated within the standard UIKit view. I found this Circular Progress Bar, designed by Geng Gao, which fits the bill perfectly.

This element is composed of two text labels (the title and subtitle) and a circular completion indicator that is intended to fill a gray track as the task progresses. We’ll start by creating a new Xcode project and naming it CircularProgressBar.

File > New > Project > Single View Application

As we will be creating a custom UIView, we need to extend UIKit’s default UIView with a new class: CircularProgressBar.

import UIKit
class CircularProgressBar: UIView {
}

There are two ways of adding labels to our design: by code or with a .xib file.

To avoid any potential confusion further down the line, the terms ‘xib’ and ‘nib’ are often used interchangeably. NIB comes from ‘NeXTSTEP Interface Builder’, Apple’s now discontinued OS. While .nib files have been replaced with .xib files, developers still refer to them as ‘nibs’.

I like to create my iOS custom views using .xib files because they require less coding and are easier to make changes to. So let’s do that and also name it CircularProgressBar.

File > New > File and select View

We will select CircularProgressBar.xib in the navigator, and then define the file owner for the .xib to our class extension: CircularProgressBar

Wait, what’s a file owner? StackOverflow supplies a more elegant explanation than I ever could: “The File Owner is an instantiated, runtime object that owns the contents of your .nib and its outlets/actions when the .nib is loaded. It can be an instance of any class you like.”

With that understood, we will hide the status bar and set the size of the .xib to ‘freeform’. This way we can change the dimensions so the view has a similar size to the design. In this case 300 x 300 pixels.

For clarity, let’s make the background red so it stands out. We’ll also add the title and subtitle labels.

We then need to write the following code so CircularProgressBar loads the .xib file we just created:

Next, mark the UIView as @IBDesignable and add it to the Main.storyboard to see how it renders. If you are not sure what IBDesignable is, take a look at this awesome post from NSHipster.

To add CircularProgressBar to the ViewController in the Main.storyboard we have to add a standard UIView and then change its class to CircularProgressBar. If everything goes well, you should see your custom view rendered in the ViewController:

Creating CALayers

So far, we have created a custom UIView that loads a .xib file and renders in the interface builder. Now we start getting into the cool stuff: Core Animation layers.

CALayer is shorthand for Core Animation: a framework that provides all the tools required to render graphics and animations. This huge framework can sometimes be overwhelming and impractical so Apple built the simpler UIKit on top of it. UIKit is an easy way to create simple views like UILabels, UITextViews, etc.

When creating custom designs we need to leverage the power of the Cora Animation framework through CALayers. Let’s start by simply drawing the circumference of a circle. Create a new layer and name it BorderLayer.

File > New > File > Cocoa Touch Class

Let’s add a new layer to CircularProgressBar. To do this, we will create a method called commonInit() and call it right after loadViewFromNib() in our initializers. In commonInit() create a new instance of our BorderLayer and add it to the layer of our view.

If you run the app, you’ll see that nothing appears to have changed. The layer is there, but we are not able to see it because it has no size, no color and no drawings on it. So let’s add in these properties!

1. Create an instance of BorderLayer and store it in a constant called ‘darkBorderLayer’.

2. Add ‘darkBorderLayer’to the view’s layer.

3. This will override layoutSubviews(). This method is called when all the sizes have been resolved, the size of your view is going to be its final size. So this is the perfect time to set the size of your layer.

4. Call setNeedsDisplay() to notify the system that the content of the layer needs to be redrawn.

There is just one thing missing: actually drawing something in the BorderLayer! This is done by overriding the method draw(in ctx: CGContext). I like to think of CGContext as a whiteboard where I can make my drawings.

The original design consists of two overlapping circles: a gray circle underneath a green one. It is the latter which shows the task progress. To achieve this effect, we need to create one more layer and store it in progressBorderLayer. By the way, you can add as many layers as you want. You just need to keep in mind that layers are stacked so the most recent label will cover any previous ones.

Lastly, we will refactor the BorderLayer so we can set the color, size, start angle and end angle from outside of the class:

In Swift, @NSManaged is how you tell the compiler that this is actually a @dynamic objective-c variable. Essentially, @dynamic tells Core Animation to track the property changes and then call different methods from our layers. A deeper explanation is outside the scope of this article – so you’ll just have to trust me!

Now let’s add the progressBorderLayer to our view and set the correct colors used in design template.

Updating the Progress Bar

Nearly there! We’re going to change the value of the progress bar in our view. This is done by updating the green line layer. We’re also going to add in a UISlider to the main storyboard, so we can test the progress indicators behavior.

Connect the UISlider to a ‘Value Changed’ action and set the minimum and maximum values for the slider to 0 and 100. I marked in the image where we want the green line to start filling the gray track: that’s our 0 value.

As you may have noticed, in BorderLayer we are using a method called addArc to draw the border of the circle. This method receives two parameters that we need to pay special attention to: the startAngle and endAngle. You can likely take a guess at what they do from their name, but it’s important to know that they receive their values as radians. Radians are just a way of measuring angles. To work with them, we need to build a function that receives a number and returns the equivalent value of that number as a radian. This function will transform, for example, 30% to its equivalent radian. Once we have the radian value we can use it in addArc to draw the arc:

We are also going to change the startAngle and endAngle of our layers using our two new constants called startAngle and endAngle. Go to the commonInit() method in CircularProgressBar and change the angles.

The above creates an @IBInspectable variable called progress. We are going to use the didSet observer, that is called immediately after the new value is stored, to update our layer’s endAngle.

The UISlider should update the progress property of our view. To do this, create an @IBOutlet variable for CircularProgress bar and connect it to the main Main.storyboard view. Name it circularProgressBar. We will then create a function that we will call every time the user moves the slider. The function receives as a parameter the UISlider object with the current value (between 0 and 100). That value is then sent to the progress property in our view.

CALayers are lazy, and they don’t like to redraw themselves. This is a built-in optimization to avoid redrawing a layer if no property of the layer has changed. So, if we want progressBorderLayer to redraw everytime the endAngle property is updated we need to add the following method to BorderLayer:

Conclusion

Hopefully, this tutorial has given you the tools and know-how to create your own iOS custom views in UIKit.

Loading an .xib file in a UIView extension is in itself a great timesaver. It allows you to compose your view using the UI tools you’re already familiar with, while still keeping view logic in a custom class. Then, by keeping the logic for connecting user interaction to your view’s properties in a view controller, you improve your app’s maintainability and are able to reuse your custom views anywhere.

Are you searching for your next programming challenge?
Scalable Path is always on the lookout for top-notch talent. Apply today and start working with great clients from around the world!

About the author

Hi. I'm a software engineer who studied IT engineering at university (a 5 year degree) where I learned everything from math and chemistry to computer networks, software development, computer architecture and lots of other interesting stuff.

After university I started working at a great company where I worked mainly as an iOS developer. As I was pretty interested in Ruby, I had the chance to work as a backend developer using different web frameworks like Rails, Grape and Sinatra. While working at this company, I also collaborated in open source projects like XMPPFramework the most used framework for XMPP under iOS or macOS. Also I wrote some blog posts to help iOS developers to get started in the XMPP world.

In my free time I enjoy playing with my nephew, scuba diving, going to the gym and also enjoy developing open source libraries (I created a couple of interesting ones!) or reading about new technologies or even working a side project.