Making custom UISwitch (Part 1)

Factory.hr
8 min readDec 19, 2016

--

Making custom UI elements is one of my favorite things to do in whole iOS development even if UIKit sometimes makes things harder to do than they should be. In my relatively short career as an iOS developer, I’ve come across a few custom UI elements created by various developers across various projects. Some of them were really good but most of them were just a couple of UIViews and UIButtons on a pile created on a hurry through the storyboard or the xib files. I don’t have to tell you how difficult maintenance of this kind of “custom” UI elements can be, especially if you’re not the original creator of those masterpieces.

If you are interested in how to make a totally reusable and customizable UI element, keep reading. This time we will create a simple custom UISwitch implementation that supports various options for customization. Yes, even more customizable than the default UISwitch.

Let’s see how the default UISwitch looks like and see what are some of the basic elements we need for our custom implementation.

The OFF state

The ON state

According to the pictures above, it’s clear that we need a couple of things and those are: 4 colors, couple of views, corner radius for views — and we can start coding right away.

Lets first start by creating a new class, call it whatever, mine is called “CustomSwitch”, and set its subclass as UIControl. If you’re not familiar with UIControl, just remember that UIControl is a class that implements common behavior for visual elements that convey a specific action or intention in response to user interaction. So whenever you need your custom UI element to respond to user interaction, go with UIControl subclass.

Now that’s out of the way, lets first create a few of the public properties:

public var onTintColor = UIColor(red: 144/255, green: 202/255, blue: 119/255, alpha: 1)

public var offTintColor = UIColor.lightGray

public var cornerRadius: CGFloat = 0.5



public var thumbTintColor = UIColor.white

public var thumbCornerRadius: CGFloat = 0.5

public var thumbSize = CGSize.zero



public var padding: CGFloat = 1

I hope the names of the properties are self-explanatory, those will determine the look of our custom switch. Let’s add a couple of more properties:

public var isOn = true

public var animationDuration: Double = 0.5

The "isOn" property is used for keeping track of the switch state, so that we know what the user selected and how to animate the thumb view of the switch. Lets now add a few private properties, including the view for the thumb (the white circular thing that moves when the user taps on the switch).

fileprivate var thumbView = UIView(frame: CGRect.zero)

fileprivate var onPoint = CGPoint.zero

fileprivate var offPoint = CGPoint.zero

fileprivate var isAnimating = false

Please note that we have private and public properties because we want to hide some of the implementation details of the class, and we want to specify a preferred interface through which the code can be accessed and used. For example, the "onPoint" property is an essential part of the view animation so we don't want to give someone the ability to tinker with it.

Now that we have all of our properties set up, it's time to do some UI work, so let's begin with creating 2 new methods.

First method is the clear method. Its intent is to remove everything from the view hierarchy in case we need to reset our UI. The implementation looks like this:

private func clear() {
for view in self.subviews {
view.removeFromSuperview()
}
}

Next is the setupUI method that we use for initial configuration of the UI, its implementation is straight forward.

func setupUI() {
self.clear()
self.clipsToBounds = false
self.thumbView.backgroundColor = self.thumbTintColor
self.thumbView.isUserInteractionEnabled = false
self.addSubview(self.thumbView)
}

As you can see, not much is happening here, just some light configuration for the thumb view, and adding the thumb view to the subview. Please note that the "self.clear()" is called here before any configuration is done. Now that initial setup of the view is done, let's start with some layout work.

First we need to override one function that is called layoutSubviews, and in this example we will use this function to set the frames of our view and its subviews directly.

public override func layoutSubviews() {
super.layoutSubviews()
}

It's very important to call super.layoutSubviews() before any custom code, otherwise we'll experience some unintended consequences and our layout will not work as intended.

Implementation of this method looks like this:

public override func layoutSubviews() {
super.layoutSubviews()
if !self.isAnimating {
self.layer.cornerRadius = self.bounds.size.height * self.cornerRadius
self.backgroundColor = self.isOn ? self.onTintColor : self.offTintColor


// thumb managment

let thumbSize = self.thumbSize != CGSize.zero ? self.thumbSize : CGSize(width:
self.bounds.size.height - 2, height: self.bounds.height - 2)
let yPostition = (self.bounds.size.height - thumbSize.height) / 2

self.onPoint = CGPoint(x: self.bounds.size.width - thumbSize.width - self.padding, y: yPostition)
self.offPoint = CGPoint(x: self.padding, y: yPostition)

self.thumbView.frame = CGRect(origin: self.isOn ? self.onPoint : self.offPoint, size: thumbSize)

self.thumbView.layer.cornerRadius = thumbSize.height * self.thumbCornerRadius

}

}

Note that we are here making sure that the animation is not active when the view is performing layout work. Now, to see what we have done, go to your view controller or view and create a new instance of our new custom switch and add it to the subview. Your code should look something like this:

let myCustomSwitch = CustomSwitch(frame: CGRect(x: 50, y: 50, width: 50, height: 30))
self.view.addSubview(myCustomSwitch)

Start the project and if everything builds successfully, we should get something like this:

Looks kinda like the original UISwitch, let’s go back to the setupUI() method and add some shadow to the thumbview like this:

self.thumbView.layer.shadowColor = UIColor.black.cgColor
self.thumbView.layer.shadowRadius = 1.5
self.thumbView.layer.shadowOpacity = 0.4
self.thumbView.layer.shadowOffset = CGSize(width: 0.75, height: 2)

Build and run, now we have less 2D element that looks much nicer with some shadow:

Let’s round up this tutorial by adding some basic animations.

Create new method that will be called animate() and add following code to its body:

private func animate() {   self.isOn = !self.isOn   self.isAnimating = true   UIView.animate(withDuration: self.animationDuration, delay: 0, usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5, options: [UIViewAnimationOptions.curveEaseOut,
UIViewAnimationOptions.beginFromCurrentState], animations: {
self.thumbView.frame.origin.x = self.isOn ? self.onPoint.x : self.offPoint.x
self.backgroundColor = self.isOn ? self.onTintColor : self.offTintColor
}, completion: { _ in self.isAnimating = false
self.sendActions(for: UIControlEvents.valueChanged)
})}

Note that we have one function which will do 2 animations for the on state and the off state. Here we are using ? operator that determines in which direction the thumbView should animate and what color to apply to the background of the view.

Now the important question, where should we call this function? How will the switch know when to activate the animation? We could add gesture recognizer to the base class and listen to the actions, or we could add one big UIButton on the top of the view hierarchy and call the animate() method on every touchUpInside action of the UIButton.

Well, no.

Remember at the beginning of the tutorial when we sub classed the UIControl instead UIView? Well, UIControl comes with some awesome implementation of methods for managing touch events. One of those awesome methods is beginTracking and this is a perfect place for calling our animate() method.

override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
super.beginTracking(touch, with: event)
self.animate()
return true
}

Now we can start our project and see the almost finished custom UISwitch. If everything’s alright, we should get something like this:

Looks pretty good if you ask me.

Now let’s just do one more thing to make this custom switch implementation more customizable. Let’s add some property observers to some of our public properties and call some custom methods when the value of this properties changes.

Refactor your public properties and make them look like this:

public var padding: CGFloat = 1 {
didSet {
self.layoutSubviews()
}
}
public var onTintColor = UIColor(red: 144/255, green: 202/255, blue: 119/255, alpha: 1) {
didSet {
self.setupUI()
}
}
public var offTintColor = UIColor.lightGray {
didSet {
self.setupUI()
}
}
public var cornerRadius: CGFloat = 0.5 {
didSet {
self.layoutSubviews()
}
}
public var thumbTintColor = UIColor.white {
didSet {
self.thumbView.backgroundColor = self.thumbTintColor
}
}
public var thumbCornerRadius: CGFloat = 0.5 {
didSet {
self.layoutSubviews()
}
}
public var thumbSize = CGSize.zero {
didSet {
self.layoutSubviews()
}
}

Now our switch is more customizable and we can tweak its appearance.

Some of my personal customizations are:

As you can see from the examples above, you can customize every color and every shape. In the second part of this tutorial, we will show you how to make this class more customizable and how to extend some of the functionalities of the switch.

You can find all the source code of this project on Factory GitHub repository.

Want to know more?

Check our custom UISwitch part 2 and the newest part 3 — Create a framework and contribute to the iOS world!

Are you almost done with your iOS app?

Finishing the development of your new iOS app is always exciting but often it causes a stressful time when it comes to the final launch of the new app to iTunes.

We know a little bit about that, to help you out, read our blog post on What to avoid when submitting a mobile app to iTunes.

--

--

Factory.hr

We are Factory — a remote-first company focused on executing the most complex digital business transformations based on Pimcore technologies.