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 2 of 4 of this article. Click here to view the first page.

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:

uiappearance

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:

uiappearance

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.

uiappearance

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?

uiappearance

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:

uiappearance

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:

uiappearance

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:

uiappearance

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:

uiappearance

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.