Making custom UISwitch (Part 1)

As one of the leading Croatian agencies, we are always on the lookout for new exciting ways to improve our iOS development. As we come to different ways to implement some new technology, we also share some fresh ideas from our workshop!

Making custom UI elements is one of our favorite things to do in whole iOS development even if UIKit sometimes makes things harder to do than they should be. We’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 file created on a hurry through the storyboard or the xib files. We 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.
Making custom UISwitch (Part 1)
The OFF state
Making custom UISwitch (Part 1)
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 (white circular thing that moves when 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.

 The 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 the 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:

Making custom UISwitch (Part 1)

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 a less 2D element that looks much nicer with some shadow:

Making custom UISwitch (Part 1)

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

 Create a new method that will be called animate() and add the 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 a 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:

Making custom UISwitch (Part 1)

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 these 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:
Making custom UISwitch (Part 1) Making custom UISwitch (Part 1) Making custom UISwitch (Part 1) Making custom UISwitch (Part 1) Making custom UISwitch (Part 1) Making custom UISwitch (Part 1) Making custom UISwitch (Part 1)

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 our GitHub Factory repository.

Explore next

Related posts

We use cookies to optimize our website. By using our services, you agree to our use of cookies.