Frameworks, Swift

Proper Presentations

Aristotle once said:

The aim of art is to represent not the outward appearance of things, but their inward significance.

Such is the aim of our UI’s. A good UI helps users navigate the utility that the app provides them. It enhances the experience without distracting from the purpose of the application.

As developers, our job is to aid designers in delivering this experience. UIKit provides a lot of tools we can use to build our applications. I would like to discuss a UIKit protocol that has made my life as a developer so much easier: UIAppearance

UIAppearance inherits from NSObjectProtocol and its job is to customize the appearance of instances of any class that adopts the protocol. Whats great about this is that it is adopted by UIBarItem and UIView. This means that you can customize the appearance of any subclass of UIView and UIBarItem

So how do we use this? Lets start with a simple example. Suppose you are developing an application that utilizes navigation controllers. In fact lets say that there are multiple views, each with their own navigation controllers. Your designer has determined that they would like the navigation bar to be TARDIS blue. There are several solutions to this problem. A common one that I have seen developers use is to subclass UINavigationController and override its viewDidLoad method to set the navigation bar color there like so:

class MyNavigationViewController: UINavigationController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationBar.barTintColor = UIColor.tardisBlue
    }
}

This works, but it requires you to use this subclass for every instance of UINavigationController and only alters the navigation bar within the scope of this particular navigation controller. This seems inefficient (and indeed it is!).

Because UINavigationBar inherits from UIView, it conforms to the UIAppearance protocol and we can customize the appearance of every UINavigationBar outside of any particular scope. We can do this with the following line of code:

UINavigationBar.appearance().barTintColor = UIColor.tardisBlue

That one line of code sets the tin color for all navigation bars in your application. One thing to note is that these appearance changes are applied as a view enters a view. If you change an appearance while a view is displayed, nothing will happen. You will need to remove the view from the view hierarchy and then put it back. With this in mind, I recommend setting all your custom appearances in the app delegate didFinishLaunchingWithOptions method.

I personally like to create a struct called Appearance that has a single static function that sets all the appearances in my applications:

import UIKit

struct Appearances{
    static func set(){
        UINavigationBar.appearance().barTintColor = UIColor.tardisBlue
        UINavigationBar.appearance().tintColor = UIColor.white
        UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor:UIColor.white]
       ...
    }
}

Calling Appearances.set() in the didFinishLaunchingWithOptions method gives all navigation bars a blue tint, with white buttons, and white titles. And I never have to think about it again. Better yet, say your designer decides to change the button tint color, or title font, etc. With this method, you have just one location to update! Boosh, programming like a boss!

Most view properties (background color, tint color, font, etc) are accessible via the appearance protocol, though I have noticed that the autocomplete is a bit sketchy.

Another cool feature this protocol gives your apps is that you can specify custom appearances for classes when they are contained in specified other classes via the appearance(whenContainedInInstanceOf:) function. For example, say we want all of our navigation bar buttons (UIBarButtonItem‘s) to have a white tint, but if they are in a tool bar, we actually want they to be blue. If this is the case, we can refine our set() function above like so:

static func set(){
    UINavigationBar.appearance().barTintColor = UIColor.tardisBlue
    UINavigationBar.appearance().tintColor = UIColor.white
    UINavigationBar.appearance().titleTextAttributes = [NSAttributedStringKey.foregroundColor:UIColor.white]
        
    UIBarButtonItem.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).tintColor = UIColor.white
    UIBarButtonItem.appearance(whenContainedInInstancesOf: [UIToolbar.self]).tintColor = UIColor.tardisBlue
}

 

TL;DR

Use the appearance method to set app wide custom colors, fonts, etc. Be sure to set these properties before the view is loaded on screen.

import UIKit

struct Appearances{
    static func set(){
       //--- Set your custom appearance here ---
    }
}

Call Appearance.set() in your app delegate’s didFinishLaunchingWithOptions to apply these changes globally.

Sources

https://developer.apple.com/documentation/uikit/uiappearance

http://nshipster.com/uiappearance/