Today, a quick note about “back” buttons. The chevron-shaped button that appears by default in the left-hand position of a nav. bar can be a little problematic. In particular, the system-provided button doesn’t cross-fade properly with other buttons, and it can be tricky to change its text programmatically. Furthermore, there doesn’t seem to be any way to (simply) generate your own chevron-shaped buttons. To address these issues, I created a UIButton
category, presented below. This category allows you to create your own chevron-shaped buttons.
Problems
Let me first quickly explain the problems we’re trying to solve. First: cross-fade. Sometimes you want to swap out the buttons on a nav. bar with a fade animation. You might use the UINavigationController's
setLeftBarButtonItem:animated:
method to do this. Unfortunately, this method doesn’t do a proper cross-fade when a back button is involved; the back button tends to “pop”. Using setHidesBackButton:animated:
doesn’t really help, unless you’re willing to schedule two animations.
A related problem has to do with relabeling the back button. For instance, in our Demine project, the back button is relabeled from “Pause” to “Menu” when a puzzle is completed. It’s not clear how you’d do this programmatically, since:
- It’s not easy to get a pointer to the system-created back button
- That button is owned by another controller’s
UINavigationItem
, and shared among all its “children” - The
UIBarItem
documentation states that atitle
should be set before a bar item is added to a nav. bar
All in all, it seems like it’s necessary to create your own chevron-shaped buttons in some cases, and it doesn’t seem like the system provides a way to do this.
Art
We’ll need some art. Here, experimentally determined, are appropriate images for the clicked and normal states of the back button.
Code
Now, the code. First, we’ll need to declare a category in a header file:
@interface UIButton (NavButton)
- (UIButton*)configureForBackButtonWithTitle:(NSString*)title target:(id)target action:(SEL)action;
@end
And then, we’ll need to define the new method in a source file:
- (UIButton*)configureForBackButtonWithTitle:(NSString*)title target:(id)target action:(SEL)action
{
// Experimentally determined
CGFloat padTRL[3] = {6, 8, 12};
// Text must be put in its own UIView, s.t. it can be positioned to mimic system buttons
UILabel* label = [[UILabel alloc] init];
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont boldSystemFontOfSize:12];
label.textColor = [UIColor whiteColor];
label.shadowColor = [UIColor darkGrayColor];
label.shadowOffset = CGSizeMake(0, -1);
label.text = title;
[label sizeToFit];
// The underlying art files must be added to the project
UIImage* norm = [[UIImage imageNamed:@"back_norm.png"] stretchableImageWithLeftCapWidth:13 topCapHeight:0];
UIImage* click = [[UIImage imageNamed:@"back_click.png"] stretchableImageWithLeftCapWidth:13 topCapHeight:0];
[self setBackgroundImage:norm forState:UIControlStateNormal];
[self setBackgroundImage:click forState:UIControlStateHighlighted];
[self addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
// Calculate dimensions
CGSize labelSize = label.frame.size;
CGFloat controlWidth = labelSize.width+padTRL[1]+padTRL[2];
controlWidth = controlWidth>=norm.size.width?controlWidth:norm.size.width;
// Assemble and size the views
self.frame = CGRectMake(0, 0, controlWidth, 30);
[self addSubview:label];
label.frame = CGRectMake(padTRL[2], padTRL[0], labelSize.width, labelSize.height);
// Clean up
[label release];
return self;
}
Invocation
To create a new button with this code, you might write something like this:
[[UIBarButtonItem alloc] initWithCustomView:[[UIButton buttonWithType:UIButtonTypeCustom] configureForBackButtonWithTitle:@"Menu" target:self action:@selector(dismiss)]];
Caveats
There might be an easier way to do this, but I can’t see it right now. This approach does represent a bit of a hack; if AAPL changes the appearance of navigation bars/back buttons in future releases of iPhone OS, the buttons produced by this method won’t look right. (I guess you’d need new code/art that matches the new system look and, at least transitionally, introspective code that figures out the system version and invokes the appropriate code with the appropriate art.) Ah well. Life: Still imperfect.