magnuskahr

writing code

Creating custom views the Swift way

There are many ways to create custom view in Swift, but this is my got to way, since I do not have to care about any initialization, and just let Swift do its magic.

The method is based on lazy initialization and setters. Let's say we wanna make a CircleView, where we will be able to set the color of the circle.

class CircleView: UIView {
    private lazy var circleView: UIView = {
        let circle = UIView(frame: bounds)
        circle.layer.cornerRadius = bounds.width / 2
        addSubview(circle)
        return circle
    }()
}

Here we simply scope the creation of the added view to out custom view, to the definition of the added view. We see that by using a lazy loaded view, we can generate it and add it when needed. But why does it need to be lazy? Well because we rely on bounds, which at creation time haven't been set yet.

Trying to run this code, and adding the CircleView to a viewcontroller, we will not get any results yet; just a blank screen - this is due to circleView never has been called, hence why it haven't added itself to the superview.

So why did we make it private? Now we can't access it! Well because if we let outsiders access it, they can do whatever they want, instead we wanna create a circleColor property on `CircleView.

var circleColor: UIColor = .red {
    didSet {
        circleView.backgroundColor = circleColor
    }
}

By using the didSet {} property observer, we can react to changes, and when calling circleView.backgroundColor the first time, it will make sure to create and add the view; and since it lazy, from now on we just edit the backgroundcolor.

That is it! The example of creating a circle view, could have been done in many ways, but this was a quick show how it could be done in this way, and how the method works to create more advanced subview.

Now one can create a CircleView by doing:

let circleView = CircleView(frame: view.bounds)
circleView.circleColor = .red
view.addSubview(circleView)
circleView.circleColor = .blue

And it can even change the circleColor after it has been added! Full code, with Storyboard designable optimisations below.

import UIKit

@IBDesignable
class CircleView: UIView {

    @IBInspectable var circleColor: UIColor = .red {
        didSet {
            circleView.backgroundColor = circleColor
        }
    }
    
    private lazy var circleView: UIView = {
        let circle = UIView(frame: bounds)
        circle.layer.cornerRadius = bounds.width / 2
        addSubview(circle)
        return circle
    }()
    
}