Style vs. Substance. Message vs. Medium. Rhetoric vs. Dialectic.
Is beauty merely skin deep, or is it somehow informed by deeper truths? What does it mean for something to possess good design? Are aesthetic judgments relative, or absolute?
These are deep questions that have been pondered by philosophers, artists, and makers alike for millennia.
And while we all continue our search for beauty and understanding in the universe, the app marketplace has been rather clear on this subject:
Users will pay a premium for good-looking software.
When someone purchases an iPhone, they are buying into Apple’s philosophy that things that work well should look good, too. The same goes for when we choose to develop for iOS—a sloppy UI reflects poorly on the underlying code.
It used to be that even trivial UI customization on iOS required AppStore-approval-process-taunting ju-ju like method swizzling. Fortunately, with iOS 5, developers were given an easier way:
UIAppearance allows the appearance of views and controls to be consistently defined across the entire application.
In order to have this work within the existing structure of UIKit, Apple devised a rather clever solution:
UIAppearance is a protocol that returns a proxy that will forward any configuration to instances of a particular class. Why a proxy instead of a property or method on
UIView directly? Because there are non-
UIView objects like
UIBarButtonItem that render their own composite views.
Appearance can be customized for all instances, or scoped to particular view hierarchies:
+appearance: Returns the appearance proxy for the receiver.
+appearanceWhenContainedIn:(Class <UIAppearanceContainer>)ContainerClass,...: Returns the appearance proxy for the receiver in a given containment hierarchy.
To customize the appearance of all instances of a class, you use
appearance()to get the appearance proxy for the class. For example, to modify the tint color for all instances of UINavigationBar:
UINavigationBar.appearance().tintColor = myColor
[[UINavigationBar appearance] setTintColor:myColor];
To customize the appearances for instances of a class when contained within an instance of a container class, or instances in a hierarchy, you use
appearanceWhenContainedInInstancesOfClasses(_:)to get the appearance proxy for the class (the older, variadic
appearanceWhenContainedInmethod has been deprecated):
UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UINavigationBar.self]) .tintColor = myNavBarColor UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UINavigationBar.self, UIPopoverController.self]) .tintColor = myNavBarColor UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UIToolbar.self]) .tintColor = myNavBarColor UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UIToolbar.self, UIPopoverController.self]) .tintColor = myNavBarColor
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class]]] setTintColor:myNavBarColor]; [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class], [UIPopoverController class]]] setTintColor:myPopoverNavBarColor]; [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UIToolbar class]]] setTintColor:myToolbarColor]; [[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UIToolbar class], [UIPopoverController class]]] setTintColor:myPopoverToolbarColor];
Determining Which Properties Work With
One major downside to
UIAppearance’s proxy approach is that it’s difficult to know which selectors are compatible.
In order to find out what methods work with
UIAppearance, you have to look at the headers:
$ cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/ Developer/SDKs/iPhoneOS*.sdk/System/Library/Frameworks/UIKit.framework/Headers $ grep -H UI_APPEARANCE_SELECTOR ./* | sed 's/ __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0) UI_APPEARANCE_SELECTOR;//'
UIAppearance looks for the
UI_APPEARANCE_SELECTOR macro in method signatures. Any method with this annotation can be used with the
For your convenience, here is the list of properties as of iOS 7.0.
<UIAppearance> in Custom UIView Subclasses
Much like how
#pragma are marks of quality in Objective-C code, having custom UI classes conform to
UIAppearance is not only a best-practice, but it demonstrates a certain level of care being put into its implementation.
Peter Steinberger has this great article, which describes some of the caveats about implementing
UIAppearance in custom views. It’s a must-read for anyone who aspires to greatness in their open source UI components.
Another major shortcoming of
UIAppearance is that style rules are imperative, rather than declarative. That is, styling is applied at runtime in code, rather than being interpreted from a list of style rules.
Yes, if there’s one idea to steal from web development, it’s the separation of content and presentation. Say what you will about CSS, but stylesheets are amazing.
Stylesheet enthusiasts on iOS now have some options. Pixate is a commercial framework that uses CSS to style applications. NUI, an open-source project by Tom Benner, does much the same with a CSS/SCSS-like language. Another open source project along the same lines is UISS by Robert Wijas, which allows
UIAppearance rules to be read from JSON.
Cocoa developers have a long history of obsessing about visual aesthetics, and have often gone to extreme ends to achieve their desired effects. Recall the Delicious Generation of Mac developers, and applications like Disco, which went so far as to emit virtual smoke when burning a disc.
This spirit of dedication to making things look good is alive and well in iOS. As a community and as an ecosystem, we have relentlessly pushed the envelope in terms of what users should expect from their apps. And though this makes our jobs more challenging, it makes the experience of developing for iOS all the more enjoyable.
Settle for nothing less than the whole package. Make your apps beautiful from interface to implementation.