I'm using the iOS 5 UIAppearance protocol to use a custom navigation bar back button as seen here. Unlike other methods I've found, this one is the best since it preserves the default back button animations and behaviours.
The only problem is that I can't change its size or set it to not clip subviews. Here's what's happening:
A is intended behaviour, B is default style, C is the clipped result.
Unfortunately, it's not as easy as setting UIBarButtonItem to clipsToBounds=NO.
Does anyone know how to solve this issue? Thanks!
As you've discovered, UIAppearance
proxies won't let you adjust the dimensions of the button itself: the list of supported appearance methods for UIBarButtonItem
is given in the documentation, and whilst it includes metrics for the title label itself the button is off-limits.
For what it's worth, the button itself is actually 44 pts (88 pixels, if you're in retina) high in terms of touchable area, it's just the button graphic is smaller than that.
If you're dead set on what amounts to a 3 pt difference in height then the best option is probably going to be to create your own custom button and use the UINavigationItem
setLeftBarButtonItem
method. As you point out, you're going to need to implement a short method attached to that button that implements popNavigationController
to ensure the correct behavior remains.
Update: I've just found out that if you keep your back button around you can, in fact, animate it to have it slide in a similar fashion to a standard back button. In the code below self.backButton
is the UIButton
you would have used in your initWithCustomView
UIBarButtonItem
method.
- (void)viewWillDisappear:(BOOL)animated {
[UIView animateWithDuration:0.3
animations:^{
self.backButton.frame = CGRectMake(self.backButton.frame.origin.x+100,
self.backButton.frame.origin.y,
self.backButton.frame.size.width,
self.backButton.frame.size.height);
}];
}
...this will trigger whenever the view controller disappears (ie, when popped and pushed), but you could hook into the UINavigationController
delegate to only trigger it when the nav controller is popped instead. It definitely seems to move the button when controller is pushed, although I've only tested it on the simulator.
I ended up using a custom view controller class for every VC I'm using in my navigation controller.
OEViewController.h
#import <UIKit/UIKit.h>
#import "OEBarButtonItem.h"
@interface OEViewController : UIViewController
@property (nonatomic, retain) UIButton *backButton;
@property (atomic) BOOL pushed;
- (void)pushViewController:(UIViewController*)vc;
@end
OEViewController.m
#import "OEViewController.h"
#define ANIMATION_DURATION 0.33
@implementation OEViewController
@synthesize backButton, pushed;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
pushed=YES;
self.navigationItem.hidesBackButton = YES;
backButton = [OEBarButtonItem backButtonWithTitle:[(UIViewController*)[self.navigationController.viewControllers objectAtIndex:[self.navigationController.viewControllers count]-2] title]];
[backButton addTarget:self action:@selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (pushed) {
self.backButton.frame = CGRectMake(self.backButton.frame.origin.x+100,
self.backButton.frame.origin.y+6,
self.backButton.frame.size.width,
self.backButton.frame.size.height);
[UIView animateWithDuration:ANIMATION_DURATION
animations:^{
self.backButton.frame = CGRectMake(self.backButton.frame.origin.x-100,
self.backButton.frame.origin.y,
self.backButton.frame.size.width,
self.backButton.frame.size.height);
}];
pushed=NO;
} else {
self.backButton.frame = CGRectMake(self.backButton.frame.origin.x-100,
self.backButton.frame.origin.y,
self.backButton.frame.size.width,
self.backButton.frame.size.height);
[UIView animateWithDuration:ANIMATION_DURATION
animations:^{
self.backButton.frame = CGRectMake(self.backButton.frame.origin.x+100,
self.backButton.frame.origin.y,
self.backButton.frame.size.width,
self.backButton.frame.size.height);
}];
}
}
- (void)backButtonPressed:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
[UIView animateWithDuration:ANIMATION_DURATION
animations:^{
self.backButton.frame = CGRectMake(self.backButton.frame.origin.x+100,
self.backButton.frame.origin.y,
self.backButton.frame.size.width,
self.backButton.frame.size.height);
}];
}
- (void)pushViewController:(UIViewController*)vc {
[self.navigationController pushViewController:vc animated:YES];
[UIView animateWithDuration:ANIMATION_DURATION
animations:^{
self.backButton.frame = CGRectMake(self.backButton.frame.origin.x-100,
self.backButton.frame.origin.y,
self.backButton.frame.size.width,
self.backButton.frame.size.height);
}];
}
@end
This has the added benefit of working with iOS 4.0+ as opposed to using UIAppearance.
Thanks to @lxt for his help!