Everyone wants to make their app look unique! Well the UIAppearance protocol can help you! In iOS 5.0 Apple introduced the UIAppearance proxy API which allows you the developer to customize many of the appearance aspects of UIKit elements. I know what you’re thinking… Can’t I already do that? Well sure, but not on the same scale as UIAppearance will allow, and furthermore, changing the appearance of UI components on a per object basis gets really old, really fast.
So let’s dive into an example right quick…
Say you have an app with a UINavigationBar and you want something other than the default background color (tint color). You would probably try something similar to the following
Then you decide another part of your app needs a navigation bar and you end up writing the same code to change the background color for that one as well. This continues as time goes on and before long you end up with an unmanageable mess, leaving you sad and depressed wishing there was a better way… enter UIAppearance…
UIKit’s UIView (the basis for most UI on iOS) implements the UIAppearance protocol which basically allows appearance changes at a wider level. The two methods defined in the protocol are
+ (id)appearanceWhenContainedIn:(Class )ContainerClass, ... NS_REQUIRES_NIL_TERMINATION;
Calling appearance on UIView (or any of its subclasses) will return an appearance proxy object, on which you can call any appearance related methods (setTintColor: is one!). More specifically, as stated in UIAppearance.h, each of the methods that are available to the proxy should be tagged with the UI_APPEARANCE_SELECTOR. This lets the appearance proxy mechanism know this method is available for the proxy. For example a quick search through UINavigationBar.h shows the following methods as tagged with UI_APPEARANCE_SELECTOR
@property(nonatomic,retain) UIColor *tintColor UI_APPEARANCE_SELECTOR;
@property(nonatomic,retain) UIImage *shadowImage NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
@property(nonatomic,copy) NSDictionary *titleTextAttributes NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
- (void)setBackgroundImage:(UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
- (UIImage *)backgroundImageForBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
- (void)setTitleVerticalPositionAdjustment:(CGFloat)adjustment forBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
- (CGFloat)titleVerticalPositionAdjustmentForBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
Note that this tag can be left out and the system will still work, but UIAppearance.h specifically states to tag all methods exposed to the appearance proxy with it. So I would only count on tagged methods working.
So let’s see how to use it! How about we set the tint color of the navigation bar as we did above.
[[UINavigationBar appearance] setTintColor:myColor];
That’s it! Now all UINavigationBars in your application will have that tint color by default! So what if I only want some, but not others to have that tint color? Well, that’s where the second method declared in the UIAppearance protocol comes in.
appearanceWhenContainedIn: allows you to set appearance attributes for a particular component based on what its containing class is. Let’s say, for example, that your UINavigationBar is part of a UINavigationController subclass that you created, call it MYNavigationController. If you then call the following method
[[UINavigationBar appearanceWhenContainedIn:[MYNavigationController class], nil] setTintColor:myColor];
only UINavigationBars with a containing class of MYNavigationController will be affected, the rest will continue to live happily with the default tint color.
And so it seems UIKit skinning is not as bad as one might have initially thought!
Using UIAppearance With Your Own Controls
Yes, you can use this with your own custom controls and next we will build an app demoing the example above and using the UIAppearance protocol with a custom UIView.
- First create a new Xcode project using the single view iOS template, make sure to select the Use ARC checkbox as this tutorial assumes you are using ARC!
- Create a new UIView subclass (CMD + N), choose Objective-C class, name it MyView. Make sure you add it to the target when prompted as shown
- Open MyView.h and add the following property declaration. The tag on the end specifies it as available to the appearance proxy (it’s better API and it’s safer to include this tag even though it works without it).
@property (nonatomic, strong) UIColor *dotColor UI_APPEARANCE_SELECTOR;
- Next open MyView.m and add the following drawRect: implementation which will draw a nice looking target in it’s bounds. If you don’t understand the drawing code don’t worry its not the point of the example (it’s not great code anyway).
If you’re feeling zealous, implement something awesome in this drawRect method and I’ll change this example to use your awesome drawing code instead! Seriously!
UIColor *fillColor = (self.dotColor != nil) ? self.dotColor : [UIColor whiteColor];
CGContextRef ctx = UIGraphicsGetCurrentContext();
//draw outer rings (demo only i.e. not great code here!)
[[UIColor whiteColor] setFill];
CGContextFillEllipseInRect(ctx, CGRectInset(self.bounds, 10.0, 10.0));
CGContextFillEllipseInRect(ctx, CGRectInset(self.bounds, 20.0, 20.0));
[[UIColor whiteColor] setFill];
CGContextFillEllipseInRect(ctx, CGRectInset(self.bounds, 30.0, 30.0));
CGContextFillEllipseInRect(ctx, CGRectInset(self.bounds, 40.0, 40.0));
- Next open ViewController.h and add an import statement for MyView
- Next open ViewController.xib and add a UINavigationBar and a UIView to the view
- Select the UIView and open the Identity Inspector (CMD + Option + 3). change the Class property to MyView
- Center the MyView object in the containing view, giving it a size of 100×100. If you’re are using auto layout (on by default) you will need to create both a width constraint and a height constraint in order to stop it from stretching.
- Finally, with the MyView object selected open the attributes inspector (CMD + Option + 4) and change the background color to clear color
Now comes the interesting part
- Open AppDelegate.m and add the following implementation for application:didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// create a random color
UIColor *appearanceColor = [UIColor colorWithRed:rand()/(CGFloat)RAND_MAX green:rand()/(CGFloat)RAND_MAX blue:rand()/(CGFloat)RAND_MAX alpha:1.0];
NSLog(@"Color set by appearance proxy %@", appearanceColor);
// set both the navigationBar tintColor and our MyView dotColor on the class level
[[UINavigationBar appearance] setTintColor:appearanceColor];
[[MyView appearance] setDotColor:appearanceColor];
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
This will first create a random color, then set it to the UINavigationBar’s appearance proxy and also MyView’s appearance proxy. Build and run, your app should resemble the one shown below!
Organizing theme code for your UI can be quite the challenge, but UIAppearance can make it much more manageable by allowing you to set UI attributes in one place. Still many have taken this one step further and written CSS-like extensions built upon appearance proxies. Here is just one notable example UISS Library.
As always please don’t be shy if you have any questions or comments!