Episode #21

Customizing UINavigationBar

21 minutes
Published on June 26, 2012

This video is only available to subscribers. Get access to this video and 572 others.

In this episode, I decompose the Foursquare UI and recreate the custom navigation bar, using the iOS 5 customization APIs. You'll see how to set a custom background image, a title view that you can tap on, a custom bar button item, and a custom back button style.

Episode Links

Setting a custom background graphic

    // CustomNavBar.m 
    UIImage *navBarBg = [UIImage imageNamed:@"navigationbar.png"];
    [self setBackgroundImage:navBarBg forBarMetrics:UIBarMetricsDefault];

Creating the title view

- (void)viewDidLoad {
    self.navigationItem.titleView = [self titleView];

    ...
}

- (UIView *)titleView {
    CGFloat navBarHeight = self.navigationController.navigationBar.frame.size.height;
    CGFloat width = 0.95 * self.view.frame.size.width;
    UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, navBarHeight)];

    UIImage *logo = [UIImage imageNamed:@"logo.png"];
    UIButton *logoButton = [UIButton buttonWithType:UIButtonTypeCustom];
    CGFloat logoY = floorf((navBarHeight - logo.size.height) / 2.0f);
    [logoButton setFrame:CGRectMake(0, logoY, logo.size.width, logo.size.height)];
    [logoButton setImage:logo forState:UIControlStateNormal];

    UIImage *bubble = [UIImage imageNamed:@"notification-bubble-empty.png"];
    UIImageView *bubbleView = [[UIImageView alloc] initWithImage:bubble];

    const CGFloat Padding = 5.0f;
    CGFloat bubbleX = 
        logoButton.frame.size.width + 
        logoButton.frame.origin.x + 
        Padding;
    CGFloat bubbleY = floorf((navBarHeight - bubble.size.height) / 2.0f);
    CGRect bubbleRect = bubbleView.frame;
    bubbleRect.origin.x = bubbleX;
    bubbleRect.origin.y = bubbleY;
    bubbleView.frame = bubbleRect;

    [containerView addSubview:logoButton];
    [containerView addSubview:bubbleView];

    return containerView;
}

Creating the custom right button item

- (void)viewDidLoad {
    ...

    self.navigationItem.rightBarButtonItem = [self checkInButton];

    ...
}

- (UIBarButtonItem *)checkInButton {
    UIImage *checkInImage = [UIImage imageNamed:@"global-checkin-button.png"];
    UIImage *checkInPressed = [UIImage imageNamed:@"global-checkin-button-pressed.png"];
    UIButton *checkInButton = [UIButton buttonWithType:UIButtonTypeCustom];

    [checkInButton setBackgroundImage:checkInImage forState:UIControlStateNormal];
    [checkInButton setBackgroundImage:checkInPressed forState:UIControlStateHighlighted];

    const CGFloat BarButtonOffset = 5.0f;
    [checkInButton setFrame:CGRectMake(BarButtonOffset, 0, checkInImage.size.width, checkInImage.size.height)];

    UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, checkInImage.size.width, checkInImage.size.height)];
    [containerView addSubview:checkInButton];

    UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:containerView];
    return item;
}

Creating the custom back button item

    // CustomNavBar.m 
    + (void)initialize {
        const CGFloat ArrowLeftCap = 14.0f;
        UIImage *back = [UIImage imageNamed:@"nav-backbutton.png"];
        back = [back stretchableImageWithLeftCapWidth:ArrowLeftCap
                                        topCapHeight:0];
        [[UIBarButtonItem appearanceWhenContainedIn:[CustomNavigationBar class], nil]
                       setBackButtonBackgroundImage:back
                                           forState:UIControlStateNormal
                                         barMetrics:UIBarMetricsDefault];
        const CGFloat TextOffset = 3.0f;
        [[UIBarButtonItem appearanceWhenContainedIn:[CustomNavigationBar class], nil]
               setBackButtonTitlePositionAdjustment:UIOffsetMake(TextOffset, 0)
                                      forBarMetrics:UIBarMetricsDefault];
    }