This article is Part 3 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.
After having introduced the technology stack and taken a closer look at views, it’s time to tackle a concrete, all-too-common example of UI customization: buttons.
I’ve covered this topic in the past, but we will tackle it afresh this time, focusing on the approach that you’d most likely use if you were to accomplish this task today.
Let’s pick up the example project from where we left off last time. If you run the project in Xcode, you will see a lonely, off-center button in the simulator. Let’s start there.
Buttons in iOS
Since iOS 7, stock buttons have lost their borders, filled backgrounds, and shadows. Let’s pretend that this new look doesn’t fit the design direction that we’re going after; we want our rounded-corner, filled button look back.
If you are used to graphic editors, the most intuitive course of action is to select the element you want to change—the button—then modify its attributes on the fly until you get the desired result.
Let’s see how far this approach can take us in Xcode. In the project navigator, click the storyboard file and select the button. Select the Attributes Inspector (a tiny slider icon) from the Utilities sidebar to the right to see the customization options available in Interface Builder. If any of the sidebars are hidden, make sure to turn them on first before proceeding.
At the bottom of the attribute list, there is a View section with a Background attribute. Change the background color to a hue of blue to your liking and the tint color right below to white. Resize the button to give the text a bit of breathing room, and change the font weight to Medium in the Button section above.
So far, so good. The only thing left is changing the corner radius, which would probably be one more attribute to change, right? Sadly, that is not the case (as of Xcode 7).
Layers
Up to this point, we’ve been dealing with views, and views only. In reality, most view classes in UIKit do not expose properties that you come to expect when designing user interfaces. Corner radiuses and borders are two notable examples. This is where layers (represented by the CALayer main class) come into play.
Put simply, every view in iOS is backed by a layer object that is responsible for drawing what you see on the screen and managing its geometry (origin, position, rotation, etc). Several methods and properties on UIView subclasses act as a thin wrapper over their layer counterparts.
While a view can have only one backing layer (or root layer), you can insert any number of sublayers into its layer stack, effectively creating a layer hierarchy, where layers are nested within other layers at specific positions.
Back to our example. The easiest way in iOS to change the corner radiuses of a
view is through the cornerRadius
property of its root layer. Since this
property is not exposed directly by the view or in Interface Builder, we will
have to take matters into our own hands and write some Swift code. So, where do
we start?
Models, Views, and Controllers
Although views are going to be the bread and butter of your work as an iOS designer, you need to get familiar with a couple more concepts to do things the Apple-endorsed way.
If you look at the Project Navigator to the left, you should see a
ViewController.swift
file that was generated when we created the project. Open
the file in the editor by selecting it, and proceed to delete the comments
(lines that start with //
) and the didReceiveMemoryWarning()
section. The
end result should look like this:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
Xcode creates this file by default because Apple recommends following the MVC pattern when building iOS apps. MVC is an acronym for Model, View, Controller and is a methodology that organizes code in a project based on three different roles:
- Model: The data and logic layer of the app. There are no default model objects since they are different from one app to another.
- View: The interface that the users sees and interacts with. We’ve already covered views in the previous article.
- Controller: The glue between models and views. It ensures that the view reflects the state of the app and also translates user input into actions that manipulate this state.
Our ViewController
is a subclass of UIViewController
, the base view
controller class in iOS. As we noted in the previous article, a
subclass inherits all of the properties and methods of its superclass. In Swift,
you can create a new subclass using the following declaration syntax:
class SubclassName: SuperclassName { }
Now let’s take a closer look at what’s happening inside the subclass declaration in our example:
override func viewDidLoad() {
super.viewDidLoad()
}
There are a couple of interesting things going on in this code snippet, but
we’ll probably save the details for later to keep things digestible. Suffice it
to say that we’re overriding the viewDidLoad()
method on the superclass
(UIViewController
) using the override
keyword, then falling back to the
superclass’s implementation of the same method using the keyword super
. It is
also worth noting that viewDidLoad()
is a method that gets automatically
called by the system when the view data is loaded into memory.
In short, we can add custom code that will execute when our view loads. There is one bit that is still missing however: how do we refer to our button from within the view controller? Enter outlets.
Outlets
Outlets are the bridge between Interface Builder and code. Each control you place in IB can be represented by an outlet in your Swift files. An outlet reference can be used to modify the look of these controls and update the data they present.
To add an outlet, we need to use the Assistant Editor in Xcode, then hold Ctrl
while dragging a connection line from the control in IB to the source code file
where we want to create the outlet:
Make sure you release the connection outside the viewDidLoad()
method,
otherwise you will get errors. If done correctly, you should see this line added
in the view controller source code:
@IBOutlet weak var roundedCornerButton: UIButton!
Now that we have a way to reference our button, we can access its properties and change them from within the view controller. But how do we do that?
Rounding Them Corners
In Swift, properties can be accessed using dot notation of the format
someObject.someProperty
. For instance, to get the root layer of our button we
can use the roundedCornerButton.layer
syntax. Similarly, we can get the
cornerRadius
property of the root layer by adding .cornerRadius
to the
previous notation.
To set a new value, we can use the same syntax as above and add an equals sign to assign a new value:
override func viewDidLoad() {
super.viewDidLoad()
roundedCornerButton.layer.cornerRadius = 4
}
Before running the app to preview the results, let’s make sure the button remains centered in the screen using Auto Layout (more on that later). Make sure the button is centered in the view by manually moving it until you see 2 blue perpendicular guides, then select the Add Missing Constraints from the Auto Layout Issues menu at the bottom right of the work area:
Voilà! Run the app to see our button in full glory. You can download the updated project here.
Wrap up
This exercise can hopefully serve as a good example of how you can use both Interface Builder and Swift to customize a stock iOS control. In the future, we’d like to focus on workflows that minimize the back and forth between the two.