Advertisement
iOS SDK

iOS SDK: UIKit Theme Customization

by

Theme customization is a great way to stand out in the App Store, but it isn't always easy to achieve. This tutorial will teach you several basic UIKit customization tricks that will help distinguish your applications and create more memorable user experiences.


Overview

If you've spent any time poking around Apple's online documentation in their iOS Dev Center, then you've surely become familiar with the infamous Human Interface Guidelines (more casually referred to as the "HIG"). This document provides developers and designers of iOS applications with guidelines for how users expect to interact with Apps on the iOS platform, common usage examples, and general UI/UX principles to follow.

While there are many ways to customize the appearance of your own iOS App -from changing colors and sizes to rolling your own UI elements- it is considered best practice to follow the HIG and its suggestions for providing a concise, usable interface.

With that in mind, let's take a look at some ways that we can do just that while customizing our interface to suit a more unique visual theme.


A Photo Viewer

Most iOS developers are familiar with the common attributes of the UIView class that are easy to change, such as background color, font size, or opacity. But sometimes, tweaking these properties just doesn't get us the result we need.

So, to demonstrate more hands-on customization techniques, we'll work on a simple photo viewer app. This is what the app will look like without any customizations, and then with the changes that we'll perform:

Before/After Demo

Before we get started, you’ll need a few images which you can download here.

Also, I’ve set up the basic application that we’ll be using, which you can download here. You don’t have to worry about all of the prep work; let’s just get started customizing!


UIViews and Their Visual Properties

With our prep app, we have a few basic components:

  • UINavigationBar (variable: aNavigationBar)
  • UIImageView (variable: anImageView)
  • UILabel (variable: aLabel)
  • UISegmentedControl (variable: aSegmentControl)

We’ll start by making our background look a little classier by setting a background image. To do this, let's add a method in our "MTViewController" class called "-setupUI".

We’ll add this method just below our @synthesize declarations.

- (void)setupUI {
}

Now, we need to add our background image file to our project, so get the "BG-pattern.png" file from the resources that you downloaded just a minute ago, and drag it into our App. I've created a new group for our UI images called "UI Graphics".

Custom Background

With the image in place, we can go inside this "setupUI" method, and we only need to add one line:

[self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"BG-pattern.png"]]];

In order to make this change live in our App, we’ll need to call our "setupUI" method from the "viewDidLoad" method of our ViewController. I've already added a few calls to the "viewDidLoad" method, so we can just add this one at the beginning:

- (void)viewDidLoad
{
    // set up the custom UI elements
    [self setupUI];

    // build arrays for images and titles
    [self setupData];
    
    // start app with first segment selected
    [self didChangeSegmentControl:aSegmentControl];
    
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

Now, let's make our image stand out a little bit by adding a white border around it. To do this, we'll be working with CALayers, which means that we need to first include the QuartzCore framework into our project.

So go to our UIKitDemo project and we’ll select our target (UIKitDemo). Then we’ll go over to the tab titled "Build Phases", and show the "Link Binary With Libraries" section.

At the bottom, we click the "+" button and choose "QuartzCore.framework".

Adding QuartzCore Framework

Finally, we have to #import Quartz into our ViewController (MTViewController.m).

#import <QuartzCore/QuartzCore.h>

With Quartz in place, we can start fiddling around with the anImageView sublayers. We’ll add the following to our "setupUI" method to add the border around our ImageView:

    [anImageView.layer setBorderWidth:5.0f];
    [anImageView.layer setBorderColor:[[UIColor whiteColor] CGColor]];

If we build and run our App now, we can see a neat, white border around our ImageView.

Image Border

Now, to make our image stick out even more, we can put a subtle shadow behind it using similar methods:

    [anImageView.layer setShadowRadius:5.0f];
    [anImageView.layer setShadowOpacity:.85f];
    [anImageView.layer setShadowOffset:CGSizeMake(1.0f, 2.0f)];
    [anImageView.layer setShadowColor:[[UIColor blackColor] CGColor]];
    [anImageView.layer setShouldRasterize:YES];
    [anImageView.layer setMasksToBounds:NO];

A few of these commands aren’t immediately obvious as to their function, so we’ll look at those line-by-line:

    [anImageView.layer setShadowRadius:5.0f];

We’re starting out by setting the amount of "blur" our shadow will have.

    [anImageView.layer setShadowOffset:CGSizeMake(1.0f, 2.0f)];

Next, we create an offset for our shadow, so that it’s not just directly behind our image.

    [anImageView.layer setShouldRasterize:YES];

This command will help with performance, as it makes sure the shadow is only drawn once and then saved as a bitmap, avoiding the need for the App to re-draw the processor intensive transparent layer.

    [anImageView.layer setMasksToBounds:NO];

Finally, we want to make sure that we're not cutting off our shadow, since it spreads out beyond our original view's bounds.

Try running the App again, and you'll see a nice shadow behind our image.

We have a lot of hard edges, so let's soften up our interface by making our UILabel (aLabel) have rounded corners.

We can do this in the same "setupUI" method beneath the UIImageView customizations. All we need is one simple line to make this work:

    [aLabel.layer setCornerRadius:15.0f];

Now, the final change to our "setupUI" method will rotate our UIImageView slightly, which will break up the "square" feeling of the rest of the layout.

We can do this with two more lines at the bottom of our "setupUI" method:

    CGAffineTransform transform = CGAffineTransformMakeRotation(.03f);
    anImageView.transform = transform;

And that's it for our "setupUI". Not too bad for only a few lines of code, right?

Image and Label Figure

UIKit Customization

With the release of iOS 5 came a new protocol for subclasses of UIViews called "appearance". This protocol allows us to modify certain aspects of our standard UIKit components as we like- such as setting custom background images, shadows, color effects and more.

A simple example of this is the UINavigationBar. We can now set custom images to change our UINavigationBars to suit our App’s themes without any hassle:

    [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];

In this case, we have a UIImage named "navBarImage", which we are setting as the default image for our UINavigationBar. The "appearance" call is going to affect our UINavigationBar class, not just a single instance. This means that our changes will be applied to all UINavigationBars that we use in this App.

To implement this change in our current Photo Viewer project, we need to go to our AppDelegate class (MTAppDelegate). We're going to the AppDelegate because we want our change to affect all of the instances of UINavigationBars in our App, so in order to ensure that our changes are made, we'll implement them immediately after the App is opened.

Let's start by adding the method to our "MTAppDelegate" class right after our @synthesize declarations, just like we did in our "MTViewController" class:

- (void)setupUI {
}

Now, we'll import our "navBar.png" image (from the image resources folder you downloaded earlier).

Import NavBar

We can then create the UIImage instance that we’ll be using for our background:

    UIImage *navBarImage = [UIImage imageNamed:@"navBar.png"];

And right after that, we can make our "appearance" call:

    [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];

Before this change is active though, we need to call our "setupUI" method from the application:didFinishLaunchingWithOptions: method.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [self setupUI];

	...
}

You can run your App now -notice the dark background in our UINavigationBar at the top.

For our next trick, things get a little bit more complicated. We're going to set custom images for our UISegmentedControl. The tricky part here is that we're not just dealing with one or two background images, but instead we have to work with specific sections of images; such as the rounded ends, selected or unselected middle sections, the dividing line between two segments, etc...

UISegment Sections

First off, if you haven’t already, import the remaining images in the image resource folder you downloaded.

UISegment Import

Just like the UINavigationBar that we modified a minute ago, we need to do two things to get our UISegmentedControl changed.

  1. Create the UIImages for our backgrounds
  2. Apply the UIImages for their respective states

I don't have enough time here to break down each of these actions step-by-step, but I’ll address some of the main ones in a minute. For now, append the following code to your "setupUI" method:

    UIImage *segmentSelected = 
    [[UIImage imageNamed:@"segcontrol_sel.png"] 
     resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
    UIImage *segmentUnselected = 
    [[UIImage imageNamed:@"segcontrol_uns.png"] 
     resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
    UIImage *segmentSelectedUnselected = 
    [UIImage imageNamed:@"segcontrol_sel-uns.png"];
    UIImage *segUnselectedSelected = 
    [UIImage imageNamed:@"segcontrol_uns-sel.png"];
    UIImage *segmentUnselectedUnselected = 
    [UIImage imageNamed:@"segcontrol_uns-uns.png"];

This creates our UIImages, and performs resizing as appropriate. You’ll notice this little bit:

	resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)

This allows us to essentially "crop" our image file by cutting into it by certain margins. In this case, we’re leaving the top as is, moving 15pts in from the left side, leaving the bottom as it is, and moving 15pts in from the right side. That leaves us with the middle section which will expand to the necessary width, but the rounded ends will stay the same- bookending our stretched middle.

For more information on "resizableImageWithCapInsets:", check out the official Apple documentation.

With our images sized and ready, we can set the different states of our UISegmentedControl. Each of the five images we just added will have a corresponding state:

    [[UISegmentedControl appearance] setBackgroundImage:segmentUnselected 
                                               forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    [[UISegmentedControl appearance] setBackgroundImage:segmentSelected 
                                               forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
    
    [[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected 
                                 forLeftSegmentState:UIControlStateNormal 
                                   rightSegmentState:UIControlStateNormal 
                                          barMetrics:UIBarMetricsDefault];
    
    [[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected 
                                 forLeftSegmentState:UIControlStateSelected 
                                   rightSegmentState:UIControlStateNormal 
                                          barMetrics:UIBarMetricsDefault];
    
    [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected 
                                forLeftSegmentState:UIControlStateNormal 
                                   rightSegmentState:UIControlStateSelected 
                                          barMetrics:UIBarMetricsDefault];

Unlike a simple UIButton, which is basically either selected or not, our UISegmentedControl can also check what the state is on either side of the current segment, but it’s not too complex once we’ve gotten the hang of it.


Conclusion

With the new features in iOS 5, and some creative work with Quartz, we can quickly and easily get our Apps to look just as we please. Some challenges that present themselves with UI customization are:

  1. Finding good graphics that are easy to manipulate.
  2. Ensuring that you keep your focus on the user experience.

It can be relatively easy to find good graphics, and there are plenty of good designers out there willing to help. It can be more difficult sometimes to keep the focus on the "why" and not just the "how" of UI customization. To quote the fictitious Dr. Ian Malcolm in Jurassic Park:

[They] were so preoccupied with whether they could that they didn't stop to think if they should.

As always, go out, try new things, tweak settings, and explore the many other ways you can customize your iOS interfaces!

Related Posts