This article is Part 4 of the Building iOS Interfaces series which tackles *the how and why of implementing iOS designs without prior native programming *experience–perfect for Web designers and developers. You can find the previous *articles here: Part 1 – Part 2 – Part 3.
In the previous article, we implemented a custom button by going back and forth between Interface Builder and Swift – a process that would quickly become strenuous if repeated over and over, unless you are building a flashlight app with a single button in the UI. Putting aside the fact that repetition is no fun, updating even the smallest details in the future would require going through every single button instance, and that’s untenable. There is a better way, and we’re here to talk about it.
The Proper Way
As we have previously mentioned in passing, subclassing is the
process of creating a new class that inherits the properties and methods of an
existing one. The subclass can optionally override the superclass’s behavior,
and that’s exactly what we need to do to customize the default look of the
UIButton
class. Let’s take a look at how this works in practice.
Open the Swiftbot project if you happen to have it locally, or download it as we have previously left it off from GitHub.
Add a new file to the project by right-clicking the parent group in the project navigator, then selecting New File….
Select Source under the iOS section, then pick Cocoa Touch Class from the template collection.
Name your class RoundedCornerButton then set UIButton in the Subclass of field. Leave the rest unchanged. On the topic of naming, classes in Swift are named in CamelCase. It’s considered good practice to pick a name that describes the purpose of the class.
In the new Swift file, go ahead and delete all the comments – lines starting
with //
– the end result should look like this:
import UIKit
class RoundedCornerButton: UIButton { }
This snippet above is the bare minimum needed to create a subclass in Swift. As
introduced in the first article, the import UIKit
part serves to
give us access to the APIs defined in that framework, namely the UIButton
class in this case.
Creating a subclass is not enough however, since Interface Builder still considers our button a UIButton. Additionally, our subclass will remain a carbon-copy of its superclass until we add some implementation code.
Classes & Instances
We’ve previously noted that each control is represented by a UIKit class in Swift. What we’ve left out however, is that a class is nothing more than a blueprint defining how a view object should look and behave. In other words, they are of little use on their own.
This is where instances come into play. An instance is an object that was built
following the specifications provided by a given class. In our example, the
button we added in IB is an instance of the RoundedCornerButton
class.
Notice how the UIButton
class states that every button should have a
buttonType
property, without actually setting its value. It’s up to the
instance to decide what type of button it wants to be.
Now let’s make our button an instance of the new subclass.
In the storyboard, select the button and look for the ID card icon in the Utilities sidebar to the right. This takes you to the Identity inspector, where you can change attributes that are unique to this button instance, such as the class and the identifier.
In the Class field, type the name of the subclass we created earlier. This
makes this specific button an instance of the RoundedCornerButton
class,
meaning that any custom behavior we add in code will be applied to it.
Now that we got this out of the way, let’s remove the outlet connection that
we’ve created in the previous article since we will no longer need
direct access to our button instance in the view controller. We can achieve that
in several ways, the easiest of which would be heading to Connections
inspector and clicking the x button next to the roundedCornerButton
outlet
in the Referencing Outlets section.
With the outlet gone, we need to remove all references to the button in our
ViewController.swift
. Go ahead and delete all the code in the class
declaration so as it ends up looking like this:
class ViewController: UIViewController { }
We are now almost done with Interface Builder in this exercise. Before we go back to our subclass however, we need to shed some light on how you typically approach using subclasses as a way to extend the stock UIKit controls.
The Subclassing Game Plan
When it comes to subclassing, the most common – and often challenging – task is figuring out what methods and properties to override and in which order your execute your custom code. If things go wrong, and they will, it’s often because you are overriding the wrong method or doing things in the wrong order.
For UIView subclasses, you typically want your custom styles to be applied as soon as the view is loaded. Common methods to override include:
-
awakeFromNib()
, which is called when the view is loaded from IB. -
drawRect(_:)
, which is called when the view needs to draw itself on screen. -
layoutSubviews()
, which is called when the view needs to determine the size and position of its subviews.
There are obviously more methods than we can cover in this tutorial and, if you are curious, you can read up on each in detail on the official UIView documentation.
Override
In order to override a method in Swift, we use the override
keyword at the
beginning of the declaration like so:
class RoundedCornerButton: UIButton {
override func awakeFromNib() { }
}
We overrode awakeFromNib()
since it seems like a good place to start adding
our layer customizations. If your run the app after these changes, you will
notice that there are no noticeable changes save for the sharp corners. That’s
expected since we removed the code that set the cornerRadius
on our instance’s
layer in the view controller.
In the previous code snippet we achieved that with this line:
roundedCornerButton.layer.cornerRadius = 4
Since we are doing this directly from within the button subclass, we can
drop the roundedCornerButton
reference in the declaration above:
class RoundedCornerButton: UIButton {
override func awakeFromNib() {
layer.cornerRadius = 4
}
}
Note that layer
is equivalent to self.layer
in his case, self
being a
reference to the instance. That said, self
is rarely required in Swift, so
it’s safe to drop unless the compiler suggests it.
Go ahead and run the app. Our button should be basking again in its rounded-corner glory.
Now here is the fun part: if you duplicate the button in IB – by alt-dragging it to a new location – the new instance will look identical, without having to manually change its properties in the view controller.
There is one little issue however. Currently we’re setting the background color through IB. That means that we decide later to change all buttons in our app to a new color, we have to go through each button object in IB and manually change the background color attribute.
We can fix that by changing the property directly in the subclass so that all rounded-corner buttons are the same color:
class RoundedCornerButton: UIButton {
override func awakeFromNib() {
layer.cornerRadius = 4
backgroundColor = UIColor(red: 0.75, green: 0.20, blue: 0.19, alpha: 1.0)
}
}
If you run the app, both buttons should be using this Tall Poppy color – name courtesy of Kromatic. The same approach could be used for the font, text color, or even brand new behavior such as an in-progress state.
Closing words
Subclassing is a powerful tool to have at one’s disposal when building iOS interfaces. You can still do without it, but it will go a long way in helping you build a sane, scalable, and modular design system.