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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

uiappearance

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:

uiappearance

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:

uiappearance

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:

uiappearance

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:
uiappearance

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:

uiappearance

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:
uiappearance

You’re done with the styling. The image below shows the before and after results of the Search screen:
uiappearance

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.