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 the Navigation Bar
Open Theme.swift and add the following two properties to Theme
:
var barStyle: UIBarStyle {
switch self {
case .default, .graphical:
return .default
case .dark:
return .black
}
}
var navigationBackgroundImage: UIImage? {
return self == .graphical ? UIImage(named: "navBackground") : nil
}
These methods simply return an appropriate bar style and background image for the navigation bar for each theme.
Next, add the following lines to the bottom of apply()
:
UINavigationBar.appearance().barStyle = barStyle
UINavigationBar.appearance().setBackgroundImage(navigationBackgroundImage, for: .default)
Okay — why does this work here? Shouldn’t you be accessing a UINavigationBar
instance?
UIKit has an informal protocol called UIAppearance
that most of its controls conform to. When you call appearance()
on UIKit classes— not instances —it returns a UIAppearance
proxy. When you change the properties of this proxy, all the instances of that class automatically get the same value. This is very convenient as you don’t have to manually style each control after it’s been instantiated.
Build and run the app. Select the Dark theme and the navigation bar should now be much darker:
This looks a little better, but you still have some work to do.
Next, you’ll customize the back indicator. iOS uses a chevron by default, but you can code up something far more exciting! :]
Customizing the Navigation Bar Back Indicator
This change applies to all themes, so you only need to add the following lines to apply()
in Themes.swift:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMask")
Here you’re simply setting the image and transition mask image to be used as the back indicator.
Build and run the app. Tap one of the pets and you should see the new back indicator:
Open Images.xcassets and find the backArrow image in the Navigation group. The image is all black, but in your app it takes on the tint color of your window and it just works.
But how can iOS just change the bar button item’s image color, and why doesn’t it do that everywhere?
As it turns out, images in iOS have three rendering modes:
- Original: Always use the image “as is” with its original colors.
- Template: Ignore the colors, and use the image as a stencil. In this mode, iOS uses only the shape of the image, and colors the image itself before rendering it on screen. So when a control has a tint color, iOS takes the shape from the image you provide and uses the tint color to color it.
- Automatic: Depending on the context in which you use the image, the system decides whether it should draw the image as “original” or “template”. For items such as back indicators, navigation control bar button items and tab bar images, iOS ignores the image colors by default. You can override this behavior and change the rendering mode manually.
Head back to the app, tap one of the pets and tap Adopt. Watch the animation of the back indicator in the navigation bar carefully. Can you see the problem?
When the Back text transitions to the left, it overlaps the indicator and looks pretty bad:
To fix this, you’ll have to change the transition mask image.
Update the line where you set backIndicatorTransitionMaskImage
in apply()
, in Theme.swift, to the following:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrow")
Build and run the app. Once again tap one of the pets and then tap Adopt. This time the transition looks much better:
The text is no longer cut off and looks like it goes underneath the indicator. So, what’s happening here?
iOS uses all the non-transparent pixels of the back indicator image to draw the indicator. However, it does something entirely different with the transition mask image. It masks the indicator with the non-transparent pixels of the transition mask image. So when the text moves to the left, the indicator is only visible in the those areas.
In the original implementation, you provided an image that covered the entire surface of the back indicator. That’s why the text remained visible through the transition. Now you’re using the indicator image itself as the mask, it looks better. But if you look carefully, you’ll see the text disappeared at the far right edge of the mask, not under the indicator proper.
Look at the indicator image and the “fixed” version of the mask in your image assets catalog. You’ll see they line up perfectly with each other:
The black shape is your back indicator and the red shape is your mask. You want the text to only be visible when it’s passing under the red area and hidden everywhere else.
To do this, change the last line of apply()
once again, this time to use the updated mask:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed")
Build and run the app. For the last time, tap one of the pets and then tap Adopt. You’ll see the text now disappears under the image, just as you anticipated it would:
Now your navigation bar is pixel perfect, it’s time to give the tab bar some much-needed love.
Customizing the Tab Bar
Still in Theme.swift, add the following property to Theme
:
var tabBarBackgroundImage: UIImage? {
return self == .graphical ? UIImage(named: "tabBarBackground") : nil
}
This property will provide appropriate tab bar background images for each theme.
To apply these styles, add the following lines to apply()
.
UITabBar.appearance().barStyle = barStyle
UITabBar.appearance().backgroundImage = tabBarBackgroundImage
let tabIndicator = UIImage(named: "tabBarSelectionIndicator")?.withRenderingMode(.alwaysTemplate)
let tabResizableIndicator = tabIndicator?.resizableImage(
withCapInsets: UIEdgeInsets(top: 0, left: 2.0, bottom: 0, right: 2.0))
UITabBar.appearance().selectionIndicatorImage = tabResizableIndicator
Setting the barStyle
and backgroundImage
should be familiar by now. It’s done exactly the same way you did for UINavigationBar
previously.
In the final three lines of code above, you retrieve an indicator image from the asset catalog and set its rendering mode to .AlwaysTemplate
. This is an example of one context where iOS doesn’t automatically use template rendering mode.
Finally, you create a resizable image and set it as the tab bar’s selectionIndicatorImage
.
Build and run the app. You’ll see your newly themed tab bar:
The dark theme is starting to look more, well, dark! :]
See the line below the selected tab? That’s your indicator image. Although it’s only 6 points high and 49 points wide, iOS stretches this to the full width of the tab at run time.
The next section covers resizeable images and how they work.