In a previous post, UIImage and resizableImageWithCapInsets, I explained how to use the method resizableImageWithCapInsets in a UIImage object to set cap insets, which specify areas of an image that are not to be changed when resizing or scaling an image. That post was a segue to this post, which will use images (and cap insets) as part of the iOS 5 appearance API and customizing the navigation bar.

Change UINavigationBar Background

With the release of iOS 5, the UIAppearance protocol is used to access the appearance proxy for a class you would like to configure. Customization is done by sending messages to the target class appearance proxy. When changing the appearance of an object, all instances of the object can be updated or only specific instances within a container class.

Let’s see how this looks as it relates to updating the background color of a UINavigationBar, it’s text and the back button which will allow a user to return to a previous view controller.

The image below is the gradient that I will use for the background:

Using the above image, I created a one pixel wide image for both portrait and landscape variations of the navbar, reason being, the height of the navar bar when the device is in portrait mode is 44, whereas the height for landscape is 32.

The images in my project are NavigationPortraitBackground.png and NavigationLandscapeBackground.png, both shown below:

I now create two UIImage objects:

  1. // Create image for navigation background - portrait
  2. UIImage *NavigationPortraitBackground = [[UIImage imageNamed:@"NavigationPortraitBackground"]
  3.                               resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
  4.  
  5. // Create image for navigation background - landscape
  6. UIImage *NavigationLandscapeBackground = [[UIImage imageNamed:@"NavigationLandscapeBackground"]
  7.                               resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
// Create image for navigation background - portrait
UIImage *NavigationPortraitBackground = [[UIImage imageNamed:@"NavigationPortraitBackground"]
                              resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];

// Create image for navigation background - landscape
UIImage *NavigationLandscapeBackground = [[UIImage imageNamed:@"NavigationLandscapeBackground"]
                              resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];

To set these images as the new backgrounds for all navigation bars, here’s all I need to do:

  1. // Set the background image all UINavigationBars
  2. [[UINavigationBar appearance] setBackgroundImage:NavigationPortraitBackground
  3.                                      forBarMetrics:UIBarMetricsDefault];
  4. [[UINavigationBar appearance] setBackgroundImage:NavigationLandscapeBackground
  5.                                      forBarMetrics:UIBarMetricsLandscapePhone];
// Set the background image all UINavigationBars
[[UINavigationBar appearance] setBackgroundImage:NavigationPortraitBackground
                                     forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:NavigationLandscapeBackground
                                     forBarMetrics:UIBarMetricsLandscapePhone];

In addition to the color, I can change the appearance of the text on all navigations bars as well:

  1. // Set the text appearance for navbar
  2. [[UINavigationBar appearance] setTitleTextAttributes:
  3.     [NSDictionary dictionaryWithObjectsAndKeys:
  4.     [UIColor whiteColor], UITextAttributeTextColor,
  5.     [UIColor redColor], UITextAttributeTextShadowColor,
  6.     [NSValue valueWithUIOffset:UIOffsetMake(0, 0)], UITextAttributeTextShadowOffset,
  7.     [UIFont fontWithName:@"Verdana-Bold" size:16], UITextAttributeFont,
  8.     nil]];
// Set the text appearance for navbar
[[UINavigationBar appearance] setTitleTextAttributes:
    [NSDictionary dictionaryWithObjectsAndKeys:
    [UIColor whiteColor], UITextAttributeTextColor,
    [UIColor redColor], UITextAttributeTextShadowColor,
    [NSValue valueWithUIOffset:UIOffsetMake(0, 0)], UITextAttributeTextShadowOffset,
    [UIFont fontWithName:@"Verdana-Bold" size:16], UITextAttributeFont,
    nil]];

As the final step, I will update the back button with a custom image, which is shown below (the button is shown on a gray backdrop so you can see the white border):

Using what I described in the early post about cap insets, I can create a UIImage object and use the appearance API of the UIBarButtonItem to set the back button look. Notice that each value in the cap inset is set to 12, indicating all four corners are to hold steady at 12 pixels, even if the images stretched or resized.

  1. // Set back button for navbar
  2. UIImage *backButton = [[UIImage imageNamed:@"blueButton"]  resizableImageWithCapInsets:UIEdgeInsetsMake(12, 12, 12, 12)];
  3.   [[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
// Set back button for navbar
UIImage *backButton = [[UIImage imageNamed:@"blueButton"]  resizableImageWithCapInsets:UIEdgeInsetsMake(12, 12, 12, 12)];
  [[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];

The last step is to create a button for the user interface that will push a new view controller, which will update the navigation bar to show the custom back button created above.

  1. ...
  2.  
  3. testButton = [[UIButton alloc] initWithFrame:CGRectMake(80, 30, 160, 44)];
  4. [testButton setTitle:@"Test Button" forState:UIControlStateNormal];
  5.  
  6. // Notice cap insets are different from above
  7. UIImage *buttonImage = [[UIImage imageNamed:@"blueButton"]  resizableImageWithCapInsets:UIEdgeInsetsMake(0, 16, 0, 16)];
  8.  
  9. [testButton addTarget:self action:@selector(buttonPressed:) forControlEvents: UIControlEventTouchUpInside];
  10. [testButton setBackgroundImage:buttonImage forState:UIControlStateNormal];
  11.  
  12. ...
...

testButton = [[UIButton alloc] initWithFrame:CGRectMake(80, 30, 160, 44)];
[testButton setTitle:@"Test Button" forState:UIControlStateNormal];

// Notice cap insets are different from above
UIImage *buttonImage = [[UIImage imageNamed:@"blueButton"]  resizableImageWithCapInsets:UIEdgeInsetsMake(0, 16, 0, 16)];

[testButton addTarget:self action:@selector(buttonPressed:) forControlEvents: UIControlEventTouchUpInside];
[testButton setBackgroundImage:buttonImage forState:UIControlStateNormal];

...

An important concept to understand here, I’ve used the same image, blueButton.png, for both the custom back button as well as the button on the primary user interface – setting the cap insets specifies the rules, if you will, of how the image can be stretched, yet keep the look appropriate for the context in which the button will appear (that is, on the navbar or main UI).

A few screenshots follow that show the custom navigation bar, text and buttons in both portrait and landscape modes:

Download the Xcode Project

You can download the entire project source code here.