UIAppearance Tutorial: Getting Started
In this UIAppearance tutorial, you’ll learn how to make your app stand out by using Swift to customize the look and feel of standard UIKit controls. By Ron Kliffer.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UIAppearance Tutorial: Getting Started
25 mins
- Getting Started
- UIAppearance: Supporting Themes
- Applying Themes to Your Controls
- Applying Tint Colors
- Customizing the Navigation Bar
- Customizing the Navigation Bar Back Indicator
- Customizing the Tab Bar
- Customizing a Segmented Control
- Customizing Steppers, Sliders, and Switches
- Customizing UITableViewCell
- Customizing a Single Instance
- Automating dark theme with Solar
- Where to Go From Here?
Customizing a Segmented Control
One element that hasn’t changed yet is the segmented control that shows the currently selected theme. Time to bring that control into the wonderful world of theming.
Add the following code to the bottom of apply()
in Theme.swift:
let controlBackground = UIImage(named: "controlBackground")?
.withRenderingMode(.alwaysTemplate)
.resizableImage(withCapInsets: UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3))
let controlSelectedBackground = UIImage(named: "controlSelectedBackground")?
.withRenderingMode(.alwaysTemplate)
.resizableImage(withCapInsets: UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3))
UISegmentedControl.appearance().setBackgroundImage(controlBackground,
for: .normal,
barMetrics: .default)
UISegmentedControl.appearance().setBackgroundImage(controlSelectedBackground,
for: .selected,
barMetrics: .default)
To understand the code above, first take a look at the controlBackground image in your asset catalog. The image may be tiny, but iOS knows exactly how to use it to draw the borders of your UISegmentedControl
, as it’s been pre-sliced and is resizable.
What does sliced mean? Take a look at the following magnified model:
There are four 3×3 squares, one in each corner. These squares are left untouched when resizing the image. The gray pixels however, get stretched horizontally and vertically as required.
In your image, all the pixels are black and assume the tint color of the control. You instruct iOS how to stretch the image using UIEdgeInsets()
. You pass 3
for the top, left, bottom and right parameters since your corners are 3×3.
Build and run the app. Tap the Gear icon in the top left and you’ll see the UISegmentedControl
now reflects your new styling:
The rounded corners are gone and have been replaced by your 3×3 square corners.
Now you’ve tinted and styled your segmented control, all that’s left is to tint the remaining controls.
Close the settings screen in the app, and tap the magnifier in the top right corner. You’ll see another segmented control, along with a UIStepper
, UISlider
, and UISwitch
still need to be themed.
Grab your brush and drop cloths — you’re going painting! :]
Customizing Steppers, Sliders, and Switches
To change the colors of the stepper, add the following lines to apply()
in Theme.swift:
UIStepper.appearance().setBackgroundImage(controlBackground, for: .normal)
UIStepper.appearance().setBackgroundImage(controlBackground, for: .disabled)
UIStepper.appearance().setBackgroundImage(controlBackground, for: .highlighted)
UIStepper.appearance().setDecrementImage(UIImage(named: "fewerPaws"), for: .normal)
UIStepper.appearance().setIncrementImage(UIImage(named: "morePaws"), for: .normal)
You’ve used the same resizable image as you did for UISegmentedControl
. The only difference here is UIStepper
segments become disabled when they reach their minimum or maximum values, so you also specified an image for this case as well. To keep things simple, you re-use the same image.
This not only changes the color of the stepper, but you also get some nice image buttons instead of the boring + and – symbols.
Build and run the app. Open Search to see how the stepper has changed:
UISlider
and UISwitch
need some theme lovin’ too.
Add the following code to apply()
:
UISlider.appearance().setThumbImage(UIImage(named: "sliderThumb"), for: .normal)
UISlider.appearance().setMaximumTrackImage(UIImage(named: "maximumTrack")?
.resizableImage(withCapInsets:UIEdgeInsets(top: 0, left: 0.0, bottom: 0, right: 6.0)), for: .normal)
UISlider.appearance().setMinimumTrackImage(UIImage(named: "minimumTrack")?
.withRenderingMode(.alwaysTemplate)
.resizableImage(withCapInsets:UIEdgeInsets(top: 0, left: 6.0, bottom: 0, right: 0)), for: .normal)
UISwitch.appearance().onTintColor = mainColor.withAlphaComponent(0.3)
UISwitch.appearance().thumbTintColor = mainColor
UISlider
has three main customization points: the slider’s thumb, the minimum track and the maximum track.
The thumb uses an image from your assets catalog. The maximum track uses a resizable image in original rendering mode so it stays black regardless of the theme. The minimum track also uses a resizable image, but you use template rendering mode so it inherits the tint of the template.
You’ve modified UISwitch
by setting thumbTintColor
to the main color. You set onTintColor
as a slightly lighter version of the main color, to bump up the contrast between the two.
Build and run the app. Tap Search and your slider and switch should appear as follows:
Your app has become really stylish, but dark theme is still missing something. The table background is too bright. Let’s fix that.
Customizing UITableViewCell
In Theme.swift, add the following properties to Theme
:
var backgroundColor: UIColor {
switch self {
case .default, .graphical:
return UIColor.white
case .dark:
return UIColor(white: 0.4, alpha: 1.0)
}
}
var textColor: UIColor {
switch self {
case .default, .graphical:
return UIColor.black
case .dark:
return UIColor.white
}
}
These define the background color you’ll use for your table cells, and the text color for the labels in it.
Next, add the following code to the end of apply()
:
UITableViewCell.appearance().backgroundColor = backgroundColor
UILabel.appearance(whenContainedInInstancesOf: [UITableViewCell.self]).textColor = textColor
The first line should look familiar, it simply sets the backgroundColor
of all UITableViewCell
instances. The second line however is where things get a little more interesting.
UIAppearance
let’s you condition the changes you want to make. In this case, you don’t want to change the entire app’s text color. You only want to change the text color inside UITableViewCell
. By using whenContainedInInstancesOf:
you do exactly that. You force this change to apply only to UILabel
instances inside a UITableViewCell
.
Build and run the app, choose dark theme, and the screen should look like this:
Now this is a real dark theme!
As you’ve seen by now, the appearance proxy customizes multiple instances of a class. But sometimes you don’t want a global appearance for a control. In these cases, you can customize just a single instance of a control.
Customizing a Single Instance
Open SearchTableViewController.swift and add the following lines to viewDidLoad()
:
speciesSelector.setImage(UIImage(named: "dog"), forSegmentAt: 0)
speciesSelector.setImage(UIImage(named: "cat"), forSegmentAt: 1)
Here you’re simply setting the image for each segment in the species selector.
Build and run the app. Open Search and you’ll see the segmented species selector looks like this:
iOS inverted the colors on the selected segment’s image without any work on your part. This is because the images are automatically rendered in Template mode.
What about selectively changing the typeface on your controls? That’s easy as well.
Open PetViewController.swift and add the following line to the bottom of viewDidLoad()
:
view.backgroundColor = Theme.current.backgroundColor
Build and run the app. Select a pet, and look at the result:
You’re done with the styling. The image below shows the before and after results of the Search screen:
I think you’ll agree the new version is much less vanilla and much more interesting than the original. You’ve added 3 dazling styles and spiced up the app.
But why don’t you take it a step further? What if you helped the user by opening the app with the appropriate theme according to when he opened it? What if you switched to dark theme as the sun sets? Let’s see how we can make that happen.