Using Appearance Proxy to Style Apps

Aug24th, 20123:00 pm

In a previous post I examined how with iOS 5 it became much easier to customise the appearance of some common UIKit controls using resizable images. In this post I want to go a little further and look at how to use appearance proxies to more fully style an applications user interface.

I should mention that I am writing this post a few weeks ahead of the expected release of iOS 6. This discussion is therefore limited to only those features that are available in iOS 5.

UIAppearance Protocol

As I demonstrated in an earlier post on customising appearance with resizable images many UIKit controls have properties and methods allowing the tintColor, background images and other appearance aspects to be set. However in an application with a large number of controls it can quickly become unmanageable if you are have to directly customise each control.

Luckily Apple introduced the UIAppearance protocol with iOS 5 that has greatly reduced the work required to apply a consistent style to an application. Instead of having to customise the appearance of each UIKit control directly you can customise the appearance of a whole class of UIKit controls (e.g. all the UIButtons in an application). The UIAppearance protocol works by creating an appearance proxy for the UIKit class. Modifying the appearance of the appearance proxy modifies the appearance of all subsequent instances of that class.

To allow some flexibility you can also conditionally customise the appearance of a class based on the containing class. For example, a UIButton can be styled differently based on which view controller contains it. The easiest way to explain the UIAppearance protocol is by way of an example:

Styling An Example App

The example app that I will use for the rest of this post is not much more than a catalogue of the most common UIKit controls. Mostly the controls are not even wired-up to target:action methods so that I can skip any code not related to customising appearance. The app uses a single storyboard to define the user interface as shown below:

The application is navigation controller based with a static table view as the root view showing each of the UIKit control types that I am going to explore in this post. Selecting a row in the table pushes a new view controller containing an example of a UIKit control onto the navigation stack. The storyboard gives you a pretty good idea of how the user interface appears using the default iOS blue colour scheme but I will try to show a before and after screenshot as we customise each view.

Organising Appearance Code

It is a good idea to collect as much of the appearance code as possible into a single class or method within the app. This makes life much easier when you suddenly decide that you want to completely change the appearance of an app. The approach that I am going to take with the example app is to create a custom class (UYLStyleController) to collect all of the appearance code. The public interface for this class is as follows:

@interface UYLStyleController : NSObject
+ (void)applyStyle;
@end

The single class method +applyStyle is called from the application delegate to setup the appearance of the app before the view hierarchy is loaded. Since we are using storyboards the application delegate is pretty simple:

UINavigationBar

The first thing we do in the applyStyle class method is to customise the appearance of the navigation bar. The default navigation bar is as shown below:

To customise the navigation bar we will apply a custom background image and modify the text attributes of the title. We also need to modify the appearance of the back button when we push a view controller onto the navigation stack as shown below:

To get started we first need to create some resizable images to use for the navigation bar. I covered this approach in detail in the previous post on customising appearance with resizable images so I will briefly recap here. Since the navigation bar on the iPhone has different heights when in portrait and landscape orientations we need to create four different images as follows:

The standard resolution navigation image has two end caps each of which is 20 pixels wide separated by a 1 pixel wide strip as follows:

The landscape image is similar except that the height is only 32 pixels and of course the corresponding @2x image files have all dimensions doubled. The portrait and landscape resizable images are created using the resizableImageWithCapInsets method to specify the 20 pixel insets.

The next thing we will do is to customise the text attributes of the title. The font, text colour and shadow can all be customised by supplying a dictionary of text attributes. In this case we will just set the text colour using the UITextAttributeTextColor key:

The navigation bar now looks as shown below with our custom background image and text settings:

To complete the navigation bar customisation we need to also change the style of the back button to replace the default blue style:

Since the back button height varies with the height of the navigation bar when in landscape orientation we will need two resizable images (plus the retina @2x versions) for the back button. The images are created with a 25 pixel inset on the left and a 6 pixel inset on the right as follows:

View Controller Background

Before getting into the various UIKit controls I should probably mention one small detail about the view controller class that is used for each of the detailed views. Since the application is not doing anything useful other than illustrating how to modify appearance it uses the same view controller class (UYLRotatingViewController) for each of the views. As the name of the controller suggests the main purpose of this class is to enable interface rotation. However since it is common to each of the views we can also implement the viewDidLoad method to set the view background colour for all of the views in one place. The full implementation of the class is shown below:

UIButton

To explore how to customise the appearance of the UIButton I will use a view containing a set of buttons arranged as a numeric keypad as shown below:

The effect that I want to implement is to add a background image to each button using an approach similar to the way we customised the navigation bar. However in this case I want to use a different background image for some of the buttons. The objective is to make the buttons numbered 1-9 grey, the OK button green, the zero button orange and the reset button red. In all cases I will use resizable background images to create a gradient effect with cap insets to provide the rounded corners.

For the basic button appearance I have created a button image (steel-button.png) that is 41 pixels wide and 44 pixels high. I will not mention it again but all of these images also have @2x versions with double the dimensions. The cap inset for the rounded corners is 20 pixels on each side.

We have seen how to create a resizable image with cap insets a number of times so I will just show the code without further comment:

Now I could just get the appearance proxy object for the UIButton class and use it to set the background image. However in this case I am going to be a little more restrictive and only customise UIButton objects that are contained by the UYLRotatingViewController view controller. This is generally a good approach when customising UIButton as it turns up in lots of different places in a user interface. For example, if I use the UIButton appearance proxy directly I would also end up modifying the disclosure indicators shown in the root table view.

To specify that you only want to customise the appearance of an object when it is contained by another class (or classes) you use the appearanceWhenContainedIn method and provide a nil terminated list of appearance container classes. In this case we have just a single container class:

That takes care of the default case for the numbers 1-9 but the other buttons are a little more tricky. To customise the zero, OK and Reset buttons we make use of the fact that the appearance protocol will also work with our own custom subclasses of the standard UIKit controls. So we can subclass UIButton for each of our custom buttons and apply the appearance changes to those subclasses.

A similar approach will work for all three buttons so I will only discuss the OK button. The first thing we will do is create a custom subclass of UIButton named UYLOkButton. This subclass is trivial as it has no additional implementation code:

UIActivityIndicator, UIProgressView and UILabel

The next set of controls I am going to look at are much more straightforward. The activity view contains some UILabel items, a large white UIActivityIndicator and a UIProgressView. The default appearance is shown below:

I am going to use a mix of mostly dark and light grey colours for the various elements so I will start by defining those colours:

There is one small complication with the UISegmentedControl in that it also has an image for the divider between the segments. At present if we do nothing else our segmented control is left with an ugly default blue divider:

To fix the divider we need to create an image 1 pixel wide and 44 pixels hide to act as the divider. We can then set the divider as follows:

The documentation for UISlider suggests that you should set divider images for the various combinations of selected and normal segments. In this case I found it sufficient to just cover the default case of both segments in the normal state. The resulting appearance is shown below:

Wrapping Up

I hope that gives you an idea on how to make use of appearance proxies when customising the appearance of an iOS app. I have not covered every option for every control as once you get the idea the technique is pretty much always the same. The example Styles Xcode project that accompanies this post can be found in my GitHub Code Examples repository should you want to take a closer look.