Blog Archives

UIAppearance Is Better Than You Think

You already know about the UIAppearance protocols added to iOS. You know that UIAppearance makes it WAY simpler to customize the appearance of the common UI elements is iOS. You probably know that you can customize the appearance of the duly marked UI_APPEARANCE_SELECTOR methods on your own subclasses as a means of differentiating certain elements from others of the same type (such as specifying a class of UIBarButtonItem as a cancel button and therefore always red).

What you might not know is that it’s trivial to use UI_APPEARANCE_SELECTOR on your own custom subclasses for visual elements that are not in the standard set of appearance selectors.  Here’s a quick example of how you might use this:

Say you have a particular class of view you have all over your application that has border around it (using the CALayer backing the view).  You could hard code this look into a view subclass, or you could do it the hard way by setting this on every individual view.  You can also use a UI_APPEARANCE_SELECTOR on your custom class and set it with an appearance proxy.  This functionality and behavior falls squarely under “I can’t believe it’s this easy”.

Here’s the interface:

#import <UIKit/UIKit.h>

@interface TestView : UIView

@property (nonatomic, retain) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, retain) UIColor *borderColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, retain) UIFont *font UI_APPEARANCE_SELECTOR;
@end

Here’s the implementation:

#import "TestView.h"
#import <QuartzCore/QuartzCore.h>

@implementation TestView
@dynamic backgroundColor;
@dynamic borderColor;
@synthesize font = _font;

- (void)drawRect:(CGRect)rect
{
    NSLog(@"%@", self.font);
}

- (void)setBackgroundColor:(UIColor *)backgroundColor
{
    [super setBackgroundColor:backgroundColor];
}

- (UIColor *)backgroundColor
{
    return [super backgroundColor];
}

- (void)setBorderColor:(UIColor *)borderColor
{
    self.layer.borderWidth = 4.0;
    self.layer.borderColor = [borderColor CGColor];
}

- (UIColor *)borderColor
{
    return [UIColor colorWithCGColor:self.layer.borderColor];
}

@end

And in the App Delegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    self.window.backgroundColor = [UIColor whiteColor];

    MainViewController *mainView = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];

    self.window.rootViewController = mainView;
    [self.window makeKeyAndVisible];

    [[TestView appearance] setBackgroundColor:[UIColor blueColor]];
    [[TestView appearance] setBorderColor:[UIColor redColor]];
    [[TestView appearance] setFont:[UIFont systemFontOfSize:12]];
    return YES;
}

And here’s the result:

And the NSLog prints the font that was set:

2012-05-16 17:03:43.517 test[26634:f803] font-family: “Helvetica”; font-weight: normal; font-style: normal; font-size: 12px

What’s the advantage?

Well it depends on your development strategy and the scope of the project.  For large, running projects it’s helpful to separate as much of the presentation code (fonts and colors) from the logic of the application.  This allows you to keep all of your styles centralized in one place as opposed to littered throughout a bunch disparate classes in much the same way that CSS can separate presentation logic from the HTML structure of a website.  Additionally, if you’re writing framework level code for a static library it offers greater flexibility and code reuse where you’re not as able to edit the implementation of a class.  This of course all depends on the purpose of your app.  For once-and-done style apps it may not make quite as much sense.

Technical notes:

You will find that if you tinker around that the UI_APPEARANCE_SELECTOR macro isn’t really necessary.  In fact, the code above will work just fine without it.  The macro #define’s to nothing, so I’m not personally sure if there’s more grandiose future plans for if it’s just a placebo macro to let developers know what will be guaranteed to be supported.  In truth, virtually any display related property on a UIView can already be set using UIAppearance proxies, though like all non-official APIs Apple can change that without notice.  By taking the approach above you’re likely future proofing your code for later.  If I were to take a guess at the internals of UIAppearance it seems like a clever hack on KVC more than a real, run-time enforced protocol.

Regardless, there’s a lot of power to be exploited using this protocol and it should change the way you think about creating a great visual style for your apps.

Advertisements