How To Make a Custom Control

In this tutorial, you will implement your very own custom control. You’ll touch on such concepts as extending existing controls, designing and implementing your control’s API, and even how to share your new control with the development community.

Colin Eberhardt
May 30 2013 · Intermediate · Article · 45 mins

Version

Controls are one of the most important building blocks of any application. They serve as the graphical components that allow your users to view and interact with their application’s data. This tutorial shows you how to make a custom control that’s reusable.

Apple supplies around 20 controls, such as UITextField, UIButton and UISwitch. Armed with this toolbox of pre-existing controls, you can create a great variety of user interfaces.

However, sometimes you need to do something just a little bit different; something that the other controls can’t handle quite the way you want.

As an example, say you’re developing an application for searching property-for-sale listings. This fictional application allows the user to filter search results so they fall within a certain price range.

You could provide an interface which presents the user with a pair of UISlider controls, one for setting the maximum price and one for setting the minimum, as shown in the screenshot below:

However, this interface doesn’t really help the user visualize the price range. It would be much better to present a single slider with two knobs to indicate the high and low price range they are searching for, as shown here:

The above interface provides a much better user experience; the users can immediately see that they are defining a range of values.

Unfortunately, this slider control isn’t in the standard UI toolbox. To implement this functionality, you’d have create it as a custom control.

You could build this range slider by subclassing UIView and creating a bespoke view for visualizing price ranges. That would be fine for the context of your app — but it would be a struggle to port it to other apps.

It’s a much better idea to make this new component generic so that you can reuse it in any context where it’s appropriate. This is the very essence of custom controls.

Custom controls are nothing more than controls that you have created yourself; that is, controls that do not come with the UIKit framework. Custom controls, just like standard controls, should be generic and versatile. As a result, you’ll find there is an active and vibrant community of developers who love to share their custom control creations.

In this tutorial, you will implement your very own RangeSlider custom control to solve the problems above. You’ll touch on such concepts as extending existing controls, designing and implementing your control’s API, and even how to share your new control with the development community.

Anyhow, enough theory. Time to start customizing!

Getting Started

This section will walk you through creating the basic structure of the control, which will be just enough to allow you to render a simple range slider on screen.

Fire up Xcode. Go to File\New\Project, select the iOS\Application\Single View Application template and click Next. On the next screen, enter CERangeSlider as the product name, and fill in the other details as in the image below:

Note that you’ll use Automatic Reference Counting (ARC) in this project but not Storyboards, since this is a single-page application. Also note that a Class Prefix is set for you in this project. You can omit the “Class Prefix”, but if you do, be aware that the auto-generated names for some files will be different from what’s specified in this tutorial.

Finally, feel free to use your own “Organization Name” and “Company Identifier”. When you’re done, click Next. Then choose a place to save the project and click Create.

The first decision you need to make when creating a custom control is which existing class to subclass, or extend, in order to make your new control.

Your class must be a UIView subclass in order for it be available in the application’s UI.

If you check the Apple UIKit reference, you’ll see that a number of the framework controls such as UILabel and UIWebView subclass UIView directly. However, there are a handful, such as UIButton and UISwitch which subclass UIControl, as shown in the hierarchy below:

The UIControl class implements the target-action pattern, which is a mechanism for notifying subscribers of changes. UIControl also has a few properties that relate to control state. You’ll be using the target-action pattern in this custom control, so UIControl will serve as a great starting point.

Right-click the CERangeSlider group in the Project Navigator and select New File…, then select the iOS\Cocoa Touch\Objective-C class template and click Next. Call the class CERangeSlider and enter UIControl into the “subclass of” field. Click Next and then Create to choose the default location to store the files associated with this new class.

Although writing code is pretty awesome, you probably want to see your control rendered on the screen to measure your progress! Before you write any code for your control, you should add your control to the view controller so that you can watch the evolution of the control.

Open up CEViewController.m, and import the header file of your new control at the top of the file, as so:

#import "CERangeSlider.h"

Further down CEViewController.m, add an instance variable just below the @implementation statement:

@implementation CEViewController
{
CERangeSlider* _rangeSlider;
}

And further down still in CEViewController.m, replace the boiler-plate viewDidLoad with the following:

The above three sections of code simply create an instance of your all-new control in the given frame and add it to the view. The control background color has been set to red so that it will be visible against the app’s background. If you didn’t set the control’s background to red, then the control would be clear — and you’d be wondering where your control went! :]

Build and run your app; you should see something very similar to the following:

Before you add the visual elements to your control, you’ll need a few properties to keep track of the various pieces of information that are stored in your control. This will form the start of your control’s Application Programming Interface, or API for short.

Note: Your control’s API defines the methods and properties that you decide to expose to the other developers who will be using your control. You’ll read about API design a little later in this article — for now, just hang tight!

Adding Default Control Properties

Open up CERangeSlider.h and add the following properties between the @interface / @end statements:

Now it’s time to work on the interactive elements of your control; namely, the knobs to represent the high and low values, and the track the knobs slide on.

Images vs. CoreGraphics

There are two main ways that you can render controls on-screen:

Images – create images that represent the various elements of your control

CoreGraphics – render your control using a combination of layers and CoreGraphics

There are pros and cons to each technique, as outlined below:

Images — constructing your control using images is probably the simplest option in terms of authoring the control — as long as you can draw! :] If you want your fellow developers to be able to change the look and feel of your control, you would typically expose these images as UIImage properties.

Using images provides the most flexibility to developers who will use your control. Developers can change every single pixel and every detail of your control’s appearance, but this requires good graphic design skills — and it’s difficult to modify the control from code.

Core Graphics — constructing your control using CoreGraphics means that you have to write the rendering code yourself, which will require a bit more effort. However, this technique allows you to create a more flexible API.

Using Core Graphics, you can parameterize every feature of your control, such as colours, border thickness, and curvature — pretty much every visual element that goes into drawing your control! This approach allows developers who use your control to easily tailor it to their needs.

In this tutorial you’ll use the second technique — rendering the control using CoreGraphics.

Note: Interestingly, Apple tend to opt for using images in their controls. This is most likely because they know the size of each control and don’t tend to want to allow too much customisation. After all, they want all apps to end up with a similar look-and-feel.

In Xcode, click on the project root to bring up the project settings page. Next, select the Build Phases tab and expand the Link Binary With Libraries section. Now click the plus (+) button at the bottom left of that section you just opened.

Either search for, or look down the list for, QuartzCore.framework. Select QuartzCore.framework and then click Add.

The reason you need to add the QuartzCore framework is because you’ll be using classes and methods from it to do manual drawing of the control.

This screenshot should help you find your way to adding the QuartzCore framework if you’re struggling:

Open up CERangeSlider.m and add the following import to the top of the file.

#import <QuartzCore/QuartzCore.h>

Add the following instance variables to CERangeSlider.m, just after the @implementation statement:

These three layers — _tracklayer, _upperKnobLayer, and _lowerKnobLayer — will be used to render the various components of your slider control. The two variables _knobWidth and _useableTrackLength will be used for layout purposes.

Next up are some default graphical properties of the control itself.

In CERangeSlider.m, locate initWithFrame: and add the following code just below the code you added to initialise the instance variables, inside the if (self) { } block:

setLayerFrames sets the frame for both knob layers and the track layer based on the current slider values. positionForValue maps a value to a location on screen using a simple ratio to scale the position between the minimum and maximum range of the control.

Build and run your app; your slider is starting to take shape! It should look similar to the screenshot below:

Your control is starting to take shape visually, but almost every control provides a way for the app user to interact with it.

For your control, the user must be able to drag each knob to set the desired range of the control. You’ll handle those interactions, and update both the UI and the properties exposed by the control.

Adding Interactive Logic

The interaction logic needs to store which knob is being dragged, and reflect that in the UI. The control’s layers are a great place to put this logic.

Right-click the CERangeSlider group in the Project Navigator and select New File…. Next, select the iOS\Cocoa Touch\Objective-C class template and add a class called CERangeSliderKnobLayer, making it a subclass of CALayer.

Open up the newly added header CERangeSliderKnobLayer.h and replace its contents with the following:

First, it translates the touch event into the control’s coordinate space. Next, it checks each knob to see whether the touch was within its frame. The return value for the above method informs the UIControl superclass whether subsequent touches should be tracked.

Tracking touch events continues if either knob is highlighted. The call to setNeedsDisplay ensures that the layers redraw themselves — you’ll see why this is important later on.

Now that you have the initial touch event, you’ll need to handle the events as the user moves their finger across the screen.

First you calculate a delta, which determines the number of pixels the user’s finger travelled. You then convert it into a scaled value delta based on the minimum and maximum values of the control.

Here you adjust the upper or lower values based on where the user drags the slider to. Note that you’re using a BOUND macro which is a little easier to read than a nested MIN / MAX call.

This section sets the disabledActions flag inside a CATransaction. This ensures that the changes to the frame for each layer are applied immediately, and not animated. Finally, setLayerFrames is called to move the knob to the correct location.

You’ve coded the dragging of the slider — but you still need to handle the end of the touch and drag events.

Build and run your project, and play around with your shiny new slider! It should resemble the screenshot below:

You’ll notice that when the slider is tracking touches, you can drag your finger beyond the bounds of the control, then back within the control without losing your tracking action. This is an important usability feature for small screen devices with low precision pointing devices — or as they’re more commonly known, fingers! :]

Change Notifications

So you now have an interactive control that the user can manipulate to set upper and lower bounds. But how do you communicate these change notifications to the calling app so that the app knows the control has new values?

There are a number of different patterns that you could implement to provide change notification: NSNotification, Key-Value-Observing (KVO), the delegate pattern, the target-action pattern and many others. There are so many choices!

What to do?

If you look at the UIKit controls, you’ll find they don’t use NSNotification or encourage the use of KVO, so for consistency with UIKit you can exclude those two options. The other two patterns — delegates and target-action patterns — are used extensively in UIKit.

Here’s a detailed analysis of the delegate and the target-action pattern:

Delegate pattern – With the delegate pattern you provide a protocol which contains a number of methods that are used for a range of notifications. The control has a property, usually named delegate, which accepts any class that implements this protocol. A classic example of this is UITableView which provides the UITableViewDelegate protocol. Note that controls only accept a single delegate instance. A delegate method can take any number of parameters, so you can pass in as much information as you desire to such methods.

Target-action pattern – The target-action pattern is provided by the UIControl base class. When a change in control state occurs, the target is notified of the action which is described by one of the UIControlEvents enum values. You can provide multiple targets to control actions and while it is possible to create custom events (see UIControlEventApplicationReserved) the number of custom events is limited to 4. Control actions do not have the ability to send any information with the event. So they cannot be used to pass extra information when the event is fired.

The key differences between the two patterns are as follows:

Multicast — the target-action pattern multicasts its change notifications, while the delegate pattern is bound to a single delegate instance.

Flexibility — you define the protocols yourself in the delegate pattern, meaning you can control exactly how much information you pass. Target-action provides no way to pass extra information and clients would have to look it up themselves after receiving the event.

Your range slider control doesn’t have a large number of state changes or interactions that you need to provide notifications for. The only things that really change are the upper and lower values of the control.

In this situation, the target-action pattern makes perfect sense. This is one of the reasons why you were told to subclass UIControl right back at the start of this tutorial!

Aha! It’s making sense now! :]

The slider values are updated inside continueTrackingWithTouch:withEvent:, so this is where you’ll need to add your notification code.

Open up CERangeSlider.m, locate continueTrackingWithTouch:withEvent:, and add the following just before the “return YES” statement:

[self sendActionsForControlEvents:UIControlEventValueChanged];

That’s all you need to do in order to notify any subscribed targets of the changes.

Well, that was easier than expected!

Now that you have your notification handling in place, you should hook it up to your app.

Open up CEViewController.m and add the following code to the end of viewDidLoad:

This method simply logs the range slider values to the console window as proof that your control is sending notifications as planned.

Build and run your app, and move the sliders back and forth. You should see the control’s values in the output window, as in the screenshot below:

You’re probably sick of looking at the multi-coloured range slider UI by now. It looks like an angry fruit salad!

It’s time to give the control a much-needed facelift!

Modifying Your Control With CoreGraphics

First, you’ll update the graphics of the “track” that the sliders move along.

Right-click the CERangeSlider group in the Project Navigator and select New File…. Next, select the iOS\Cocoa Touch\Objective-C class template and add a class called CERangeSliderTrackLayer, making it a subclass of CALayer.

Open up the newly added file CERangeSliderTrackLayer.h, and replace its contents with the following:

The code above ensures that the new track layer is used — and that the hideous background colors are no longer applied. :]

There’s just one more bit — remove the red background of the control.

Open up CEViewController.m, locate the following line in viewDidLoad and remove it:

_rangeSlider.backgroundColor = [UIColor redColor];

Build and run now…what do you see?

Do you see nothing? That’s good!

Good? What’s good about that? All of your hard work — gone?!?!

Don’t fret — you’ve just removed the gaudy test colors that were applied to the layers. Your controls are still there — but now you have a blank canvas to dress up your controls!

Since most developers like it when controls can be configured to emulate the look and feel of the particular app they are coding, you will add some properties to the slider to allow customization of the “look” of the control.

Open up CERangeSlider.h and add the following code just beneath the properties you added earlier:

Here’s another breakdown of the rendering steps, with each commented section explained below:

Once a path is defined for the shape of the knob, the shape is filled in. Notice the subtle shadow which gives the impression the knob hovers above the track.

The border is rendered next.

A subtle gradient is applied to the knob.

Finally, if the button is highlighted — that is, if it’s being moved — a subtle grey shading is applied.

Build and run once again; it’s looking pretty sharp and should resemble the screenshot below:

You can easily see that rendering your control using Core Graphics is really worth the extra effort. Using Core Graphics results in a much more versatile control compared to one that is rendered from images alone.

Handling Changes to Control Properties

So what’s left? The control now looks pretty snazzy, the visual styling is versatile, and it supports target-action notifications.

It sounds like you’re done — or are you?

Think for a moment about what happens if one of the range slider properties is set in code after it has been rendered. For example, you might want to change the slider range to some preset value, or change the track highlight to indicate a valid range.

Currently there is nothing observing the property setters. You’ll need to add that functionality to your control.

In order to detect when the control’s properties have been externally set, you’ll have to write your own setter implementation.

Your first inclination might be to add some code that looks like this:

The above code generates the setters for all eight in one fell swoop. As well, it invokes the setter method that updates each individual property. redrawLayers is called for the properties that affect the control’s visuals, and setLayerFrames is invoked for properties that affect the control’s layout.

That’s all you need to do in order to ensure the range slider reacts to property changes.

However, you now need a bit more code to test your new macros and make sure everything is hooked up and working as expected.

Open up CEViewController.m and add the following code to the end of viewDidLoad:

The above method changes the track highlight colour to red, and changes the shape of the range slider and its knobs.

Build and run your project, and watch the range slider change from this:

to this:

How easy was that?

Note: The code you just added to the view controller illustrates one of the most interesting, and often overlooked, points about developing custom controls – testing.

When you are developing a custom control, it’s your responsibility to exercise all of its properties and visually verify the results. A good way to approach this is to create a visual test harness with various buttons and sliders, each of which connected to a different property of the control. That way you can modify the properties of your custom control in real time — and see the results in real time.

Where To Go From Here?

Your range slider is now fully functional and ready to use within your own applications!

However, one of the key benefits of creating generic custom controls is that you can share them across projects — and share them with other developers.

Is your control ready for prime time?

Not just yet. Here are a few other points to consider before sharing your custom controls:

Documentation – Every developer’s favourite job! :] While you might like to think your code is beautifully crafted and self-documenting, other developers will no doubt disagree. A good practice is to provide public API documentation, at a minimum, for all publicly shared code. This means documenting all public classes and properties.

For example, your CERangeSlider needs documentation to explain what it is — a slider which is defined by four properties: max, min, upper, and lower — and what it does — allows a user to visually define a range of numbers.

Robustness – What happens if you set the upperValue to a value greater than the maximumValue? Surely you would never do that yourself – that would be silly, wouldn’t it? But you can guarantee that someone eventually will! You need to ensure that the control state always remains valid — despite what some silly coder tries to do to it.

API Design – The previous point about robustness touches on a much broader topic — API design. Creating a flexible, intuitive and robust API will ensure that your control can be widely used, as well as wildly popular. At my company, ShinobiControls, we hold meetings that can last for hours where we debate every minor detail of our APIs!

There are a number of places to start sharing your controls with the world. Here are few suggestions of places to start:

GitHub – GitHub has become one of the most popular places to share open source projects. There are already numerous custom controls for iOS on GitHub. What’s great about GitHub is that it allows people to easily access your code and potentially collaborate by forking your code for other controls, or to raise issues on your existing controls.

CocoaPods – To allow people to easily add your control to their projects, you can share it via CocoaPods, which is a dependency manager for iOS and OSX projects.

Cocoa Controls – This site provides a directory of both commercial and open source controls. Many of the open source controls covered by Cocoa Controls are hosted on GitHub, and it’s a great way of promoting your creation.

Binpress – This site provides both free and paid-for controls. You can often find what you’re looking for here, but if you don’t then why not make your control and then put it on here. You never know, people might be willing to buy it if you’ve written a clean, easy-to-use API!

Hopefully you’ve had fun creating this range slider control, and perhaps you have been inspired to create a custom control of your own. If you do, please share it in the comments thread for this article — we’d love to see your creations!

Contributors

Colin Eberhardt

Colin Eberhardt has been writing code and tutorials for many years, covering a wide range of technologies and platforms. Most...

Author

In this tutorial, you will implement your very own custom control. You’ll touch on such concepts as extending existing controls, designing and implementing your control’s API, and even how to share your new control with the development community.