11 Apr 2013
iOS UI that shines forever with UIAppearance and Core Graphic

iOS UI that shines forever with UIAppearance and Core Graphic

Traditionally, developers build UI relying on resources included within the application package with a huge part of it as images, sprites, backgrounds and then by using built in API present the complex graphical interface on the screen. 

Since iOS 5 developers can finally change the tint and outlook most of elements without much trouble. The UIAppearance protocol was added to simplify custom styling of iOS application UI elements and the classes that support the UIAppearance protocol have access to an appearance selector. This selector returns the appearance proxy for the receiver. With this proxy you can call selectors like setTintColor:, setBackgroundImage:forBarMetrics:, etc.

An example below shows how to set background images for UIButton and UINavigationBar:

[[UIButton appearance] setBackgroundImage:[[UIImage imageNamed:@"metalBlack"] 
resizableImageWithCapInsets:UIEdgeInsetsMake(4.0, 4.0, 4.0, 4.0)]
forState:UIControlStateNormal]; UIImage *textureImage = [[UIImage imageNamed:@"gray_texture"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)]; [[UINavigationBar appearance] setBackgroundImage:textureImage
forBarMetrics:UIBarMetricsDefault];

You can go even farther and make more out of UIAppearance with pacing UI_APPEARANCE_SELECTOR in your own custom subclasses to access visual elements that are not in the standard set of appearance selectors. As an example if you have a concrete UIView subclass that should be styled but it does not have a needed method then to avoid hard coding parameters into a view subclass or setting parameters for every its instance you can flag your property with UI_APPEARANCE_SELECTOR in the custom class and set it up with an appearance proxy latter.

Define the interface CustomAppearanceView that has a method definition with a special flag:

@interfaceCustomAppearanceView : UIView

@property (nonatomic, retain) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;

@end

add implementation of the inteface:

@implementationCustomAppearanceView

@dynamic backgroundColor;

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

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

@end

to use place the following code in your UIApplicationDelegate delegate implementation into didFinishLaunchingWithOptions method: 

[[CustomAppearanceView appearance] setBackgroundColor:[UIColor blueColor]];

It is all as good as it gets but still to make a shiny application with that eye-catching outlook we need icons and images that are still depended on the device resolution and event interface orientation sometime.

The problem is that we need to create additional sets of graphical resources for all available screed resolutions with a substantial increase in the size of the application package and probably latter deal with post release maintenance implications (just may be retina v2 around the corner). The process demands a lot of attention and sometime gets tedious in the case amount of graphical elements is quite big especially when we need localize some of it. 
To make it in a way “written once works everywhere” at least on the same iOS platform we should crate images with system provided drawing functionality and in our case it is Core Graphic (Quartz 2D) framework. In the sample below we create an image for the button with a simple text label inside of it.

- (UIImage*) imageForButton{
    CGSize buttonSize = CGSizeMake(85.0, 38.0);
    UIGraphicsBeginImageContextWithOptions(buttonSize, NO, 0.0f);
    
    [self drawButton:buttonSize];
    
    UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return result;
}


- (void)drawButton: (CGSize) size{

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = UIGraphicsGetCurrentContext();

    UIColor* selectedKnobColor = [UIColor colorWithRed: 0.4 green: 0.4 blue: 0.6 alpha: 1];
    UIColor* gradientColor = [UIColor colorWithRed: 0.85 green: 0.85 blue: 0.85 alpha: 1];

    NSArray* gradientColors = [NSArray arrayWithObjects: 
        (id)gradientColor.CGColor, 
        (id)[UIColor whiteColor].CGColor, nil];
    CGFloat gradientLocations[] = {0, 1};
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, 
(CFArrayRef)gradientColors, gradientLocations); UIColor* shadow = gradientColor; CGSize shadowOffset = CGSizeMake(0.1, 3.1); CGFloat shadowBlurRadius = 5; NSString* buttonLabelContent = @"Button"; { UIBezierPath* buttonRectanglePath = [UIBezierPath bezierPathWithRoundedRect:
CGRectMake(0.5, 0.5, size.width, size.height) cornerRadius: 4]; CGContextSaveGState(context); CGContextSetShadowWithColor(context, shadowOffset,
shadowBlurRadius, shadow.CGColor); CGContextBeginTransparencyLayer(context, NULL); [buttonRectanglePath addClip]; CGContextDrawLinearGradient(context, gradient, CGPointMake(43, 38.5),
CGPointMake(43, 0.5), 0); CGContextEndTransparencyLayer(context); CGContextRestoreGState(context); [[UIColor lightGrayColor] setStroke]; buttonRectanglePath.lineWidth = 1; [buttonRectanglePath stroke]; CGRect buttonLabelRect = CGRectMake(1, 12, 85, 18); [selectedKnobColor setFill]; [buttonLabelContent drawInRect: buttonLabelRect
withFont: [UIFont fontWithName: @"Helvetica" size: 12]
lineBreakMode: UILineBreakModeWordWrap
alignment: UITextAlignmentCenter]; } CGGradientRelease(gradient); CGColorSpaceRelease(colorSpace); }

As a result of imageForButton we will have an image like shown below:


The code inside of drawButton method is a quite long one and making it just by typing all code on the keyboard definitely is not the way any one is looking for. 

That's where, accounting on to all said above, a very useful tool called PaintCode comes to guarantee that you do not need to spend the rest of your weekends with a some fancy icon and its Bezie curves. We are going to write a little more about PaintCode tool quite soon.
As a summary we probably need to mention that the best approach to go with is a reasonable combination of static resources and code-drawn UI elements. Since some resources like the application’s icons, launch images and iTunes resources cannot be replaces with anything else at all, therefor the common way cannot be avoided totally, but having an alternative probably may help to go over some problems. As a sample when the size of application should be significantly reduced or resource localization should be simplified.

Similar posts:


Favourite posts

What it Takes to Get an e-Commerce Site Online

Getting an e-Commerce website online might sound like a huge undertaking,...

WebView Interactions with JavaScript

WebView displays web pages. But we are interested not only in web-content...

Google Maps API for Android

Google Maps is a very famous and helpful service, which firmly entrenched...

Unit Testing with RSpec

RSpec is an integral part of Test Drive Development (TDD) and its main id...

Client side JavaScript: Knockout in practice

When developing a web application that extensively works with user input ...

Accessing Field Configurations in JIRA for changing field description

Field configuration defines behavior of all standart (system) fields and ...

A Guide for Upgrading to Ruby on Rails 4.2.0

As you might have already heard, the latest stuff for upgrading rails was...