Building iOS Interfaces: Subclassing Views

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 1Part 2Part 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.

Final Result

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….

New File

Select Source under the iOS section, then pick Cocoa Touch Class from the template collection.

Cocoa Touch Class

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.

Subclass Naming

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.

Class vs Instance

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.

Identity Inspector

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.

Set Class for Button

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.

Remove Outlet

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.

Rounded Corner Button

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.

Two Rounded Corner Buttons

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.

Two Red Buttons

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.