Consistent user interface styling with UIAppearance

One distinguishable trait all successful applications have in common is the quality of their user interface. Users first impression depends mostly on the visual aspect, and we all know how important it is to create a positive perception when the application is launched for the first time.

Visual consistency is an essential principal of good design in general and it should be methodically applied to software user interfaces. For iOS apps, Apple gave developers, in addition to the HIG, a tool that simplifies the propagation of specific aspects of the visual style across all screens. The UIAppearance protocol is available since iOS5.

How UIAppearance works?

UIAppearance is a protocol which exposes several methods, all of them returning an appearance proxy for any class that conforms to the protocol.

In Objective-C, a proxy mediates the communication between an object on one side (the caller), and a multitude of other objects on the other side (the receivers). It uses the message forwarding mechanism to dispatch messages (method invocations) to objects that the caller doesn’t “know” individually.

Many UIKit classes expose the appearance proxy through a class method. The proxy allows setting a number of visual properties to all instances of the class.

How to use UIAppearance?

Mattt Thompson published the list of iOS7 UIKit classes and their styling methods available through the appearance proxy, as well as the commands to run in the Terminal and get this list for any iOS version.

The methods should be invoked on the appearance proxy for any of the classes from the list, and the appearance proxy is obtained by calling one of the class methods:

  • -appearance: returns the proxy for all class instances in the runtime
  • -appearanceWhenContainedIn:: returns the proxy for the class instances that are part of the view hierarchy of the classes specified in the method argument. It is possible to specify multiple container classes in a list terminated by nil.

In iOS8, Apple added the methods -appearanceForTraitCollection: and -appearanceForTraitCollection:whenContainedIn, which give an additional level of customization depending on the device screen size and orientation.

Some use cases

To show how the appearance proxy works in practice, I’ve implemented a few examples which are available for download here. These examples are mostly academic; they demonstrate how the UIAppearance protocol works, but aren’t probably very useful as they are in a real application.

The sample application user interface is split is three tabs, each one representing a color and containing a view controller inside a navigation controller. This gives us several UIKit components the aspect of which we can alter using the UIAppearance proxy.

UINavigationBar background color

The navigation bar background color is changed according to the currently selected tab.
Because this specific style is not the same across the whole application, we have to implement an UIViewController subclass and override the -viewWillAppear: method to set the tintBarColor property depending on the selected tab.

In this particular case, it’s better to update the tintBarColor property directly on the navigation bar, because the change applies to a single UINavigationBar instance known by the view controller. But I used the appearance proxy to show that the color change doesn’t take effect until the navigation bar is removed, then added again to the view hierarchy, as stated by Apple in the UIAppearance documentation:

UIBarItem text attributes

The goal is to set the color of the text in the tab bar items to black, and in the navigation bar items to white.

UIBarButtonItem and UITabBarItem are subclasses of UIBarButton. I first tried to set the text attributes on the generic class using the -appearanceWhenContainedIn: proxy, but this seems to be broken in iOS8. So I resolved to setting the text attributes both on UITabBarItem and UIBarButtonItem appearance proxies with an application wide scope; the method in the next listing is invoked in the AppDelegate -application:didFinishLaunchingWithOptions: method:

UILabel in a navigation bar

I wanted to set the navigation bar title background to a specific color.
The solution is to use the -appearanceWhenContainedIn: method and pass the UINavigationBar class as an argument. But if we use the UIView proxy, which UILabel’s superclass, the background color would be also changed for the bar button, and we don’t want that.

So I subclassed UIView, added an UILabel as a subview, and used a subclass instance as the titleView for the navigation item.
Then I called the -appearanceWhenContainedIn: on the subclass:

UISlider with thumbnail icons

I had two customization goals for the UISlider:

  • change the default track color across the whole application
  • make the minimum and maximum thumbnails color dependent on the container tint color

The first requirement is fulfilled by calling in the AppDelegate the -setMinimumTrackTintColor and -setMaximumTrackTintColor methods on the UISlider appearance proxy. There seems to be a problem though in iOS8, because when we return to a tab that was previously visited, the UISlider track is not visible anymore. The workaround is to create a new slider and add it to the view hierarchy each time the tab is changed.

We meet the second requirement by using the UIImage -imageWithRenderingMode: method and passing the UIImageRenderingModeAlwaysTemplate argument when we initialize the slider minimumValueImage and maximumValueImage properties. In this case, the image is used as a mask which is filled with the tintColor of the container view.
ThetintColor is set to main views on all tabs. In the Red tab, the tint color is still the default blue, but in the Green tab I set the tintColor to green. Here is the complete listing of the -viewDidAppear: method:

Conclusion

UIApperance protocol provides an easy way to apply consistent style across the whole application, but there are a few gotchas. Maybe the workarounds I found will help the developers who run into the same problems in the future.

In the next blog post we will take a look at the UI_APPEARANCE_SELECTOR, which allows to implement custom attributes that can be changed using the appearance proxy.

 

Catalin Rosioru

 

One thought on “Consistent user interface styling with UIAppearance

Comments are closed.