Friday, February 14, 2014

UIAppearance for Fun and Profit

Building custom user interfaces is something our iOS development team encounters on a daily basis. Anything you can do to make it faster to develop or make your reusable controls easily customizable is an easy victory. UIAppearance gives you this easy victory.
While UIAppearance isn’t a brand new topic, it seems to get less love than it should. UIAppearance is a protocol available since iOS 5 that allows a developer to quickly configure the appearance of user interface controls provided by Apple. The fact that that your custom controls can take part in UIAppearance is talked about even less. This post will cover the basics of using UIAppearance and will explore adding support to your custom interface components.

UIAppearance Basics

You’ll be glad to know that UIAppearance is a straightforward API with almost no learning curve. To use it you need only know three things.
  • Properties and methods exposed to UIAppearance are decorated with the UI_APPEARANCE_SELECTOR attribute.
  • The class method +[UIControl appearance] will set the UIAppearance system to apply the specified properties when the control is created.
  • The class method +[UIControl appearanceWhenContainedIn:] will set the UIAppearance system to apply the specified properties when the control is created, but only when it is created in the specified containers.
That is all there is to it. UI_APPEARANCE_SELECTOR+ appearance+ appearanceWhenContainedIn:

Adding some style to UINavigationBar

Let’s see how this works. Say you want to style your UINavigationBar to be something a little different than the standard iOS style. First, look at the header file for UINavigationBar (you can find this by right-clicking on the symbol and choosing ‘Jump to Definition’ on it in Xcode).
Search for UI_APPEARANCE_SELECTOR. You’ll see it attached to many of the properties and methods, but not all. This is your guide to what is available byUIAppearance. In our case, the first time we see the attribute is with:
1
@property(nonatomic,retain) UIColor *tintColor UI_APPEARANCE_SELECTOR;
This means that the tintColor property is available for UIAppearance. This means I can now apply a global style affecting the tint to all UINavigationBar objects by telling the appearance proxy:
1
[[UINavigationBar appearance] setTintColor:[UIColor redColor]];
Now, no matter where a UINavigationBar is displayed within my app, it will be tinted red. Classy.
You can make this even more specific by telling the style to only apply when it is inside your own YOURCustomViewController. Consider the code:
1
2
[[UINavigationBar appearanceWhenContainedIn:[YOURCustomViewController class], nil]
                               setTintColor:[UIColor redColor]];
You’ve now told it to only apply the red tint color when a UINavigationBar is displayed inside a YOURCustomViewController. However, if I add a UINavigationBarto another view controller, it will not be tinted red. Only in the case that an instance of a UINavigationBar is in the hierarchy below an instance of aYOURCustomViewController will it be tinted red. Still classy.
All of these appearance styles can be applied as early as you like in your application. For example, you can set all your styles in -[UIApplicationDelegate application:didFinishLaunchingWithOptions:].

Supporting different iOS versions

As can often occur when working with any API, certain versions of can provide different functionality. How can we build an application level style when the available methods and properties on Apple’s controls are changing?
Thankfully, Objective-C is a dynamic language that allows runtime introspection on your objects. In a similar style to how you would ask your object if it -respondsToSelector:, you can ask a class if it’s instances respond to a method as well.
Consider this sample:
1
2
3
if ([UINavigationBar instancesRespondToSelector:@selector(setShadowImage:)]) {
    [[UINavigationBar appearance] setShadowImage:awesomeShadow];
}
The shadowImage property is new to iOS 6. Using +[NSObject instancesRespondToSelector:, we can inspect whether any instance of an object of this type will respond to this selector. Since you’ll be using the class type to apply an appearance, not an instance, this class method will tell you if this property can be used.

Using UIAppearance in your custom UI views

As stated earlier, you too can take advantage of UIAppearance in your custom views. Following the same pattern as seen above, let’s create a UIButton subclass that provides a property through UIAppearance.
I want to expose the font of the label on a UIButton. After subclassing, I’ll create a property that is decorated with `UI_APPEARANCE_SELECTOR calledtitleFont.
1
2
3
4
5
@interface TWTButton : UIButton

@property (nonatomic, strong) UIFont *titleFont UI_APPEARANCE_SELECTOR;

@end
Next, I create the setter implementation and set the title label’s font property.
1
2
3
4
5
6
7
8
9
10
11
12
13
#import "TWTButton.h"

@implementation TWTButton

- (void)setTitleFont:(UIFont *)titleFont
{
    if (_titleFont != titleFont) {
        _titleFont = titleFont;
        [self.titleLabel setFont:_titleFont];
    }
}

@end
Now, you can set its appearance as if it was always available via UIAppearance:
1
[[TWTButton appearance] setTitleFont:myFont];
For a more detailed example of this, check out TWTButton.

No comments:

Post a Comment