I have a UINavigationController
into which I push several views. Inside viewDidLoad
for one of these views I want to set the self.navigationI
You can use leftBarButtonItem instead of back button item. And to remove the default back button item set it to nil like follows;
navigationController?.navigationBar.backIndicatorImage = nil
navigationController?.navigationBar.backIndicatorTransitionMaskImage = nil
let button = UIButton.init(type: .custom)
button.imageView?.contentMode = UIViewContentMode.scaleAspectFit
button.setImage(UIImage.init(named: "top_back"), for: UIControlState.normal)
button.frame = CGRect.init(x: 0, y: 0, width: 75, height: 50)
button.addTarget(self, action: #selector(handleBackButton), for: .touchUpInside)
let barButton = UIBarButtonItem.init(customView: button)
self.navigationItem.leftBarButtonItem = barButton
If your end goal is to simply replace the image used for the back button, you can use a new method on UIBarButtonItem available in iOS 5.0:
setBackButtonBackgroundImage:forState:barMetrics:
Apple Docs: http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIBarButtonItem_Class/Reference/Reference.html
Here's a simple example that sets a custom background image for all back buttons in your app:
UIImage *toolbarBackButtonBackgroundPortrait = [[UIImage imageNamed:@"toolbarBackButtonPortrait"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 17, 0, 6)];
UIImage *toolbarBackButtonBackgroundLandscape = [[UIImage imageNamed:@"toolbarBackButtonLandscape"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 17, 0, 6)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:toolbarBackButtonBackgroundPortrait forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:toolbarBackButtonBackgroundLandscape forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
I never been able to create a proper UIBarButtonItem
with custom view and setBackBarButtonItem
.
Here's the solution i found : let net UINavigationControllerDelegate
handles everything! The trick here is to call the popViewControllerAnimated:
method of the viewController.navigationController
so you don't have to create any custom method.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if([navigationController.viewControllers count ] > 1) {
UIView *backButtonView = [[UIView alloc] initWithFrame:CGRectMake(0,0,70,35)];
UIButton *myBackButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
[myBackButton setFrame:CGRectMake(0,0,70,35)];
[myBackButton setImage:[UIImage imageNamed:@"back.png"] forState:UIControlStateNormal];
[myBackButton setEnabled:YES];
[myBackButton addTarget:viewController.navigationController action:@selector(popViewControllerAnimated:) forControlEvents:UIControlEventTouchUpInside];
[backButtonView addSubview:myBackButton];
[myBackButton release];
UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithCustomView:backButtonView];
viewController.navigationItem.leftBarButtonItem = backButton;
[backButtonView release];
[backButton release];
}
}
Set the backBarButtonItem
before pushing the viewController with the navigationController. Setting the backBarButtonItem
in viewDidLoad
doesn't work.
Say I have MyTableViewController. When the user taps a particular row I want to push AnotherViewController using the navigationController. The code looks something like this:
// in MyTableViewController's tableView:didSelectRowAtIndexPath method...
UIBarButtonItem *backButton = [[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:@"yourImage.png"]
style:UIBarButtonItemStyleBordered
target:nil
action:nil];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
[self.navigationController pushViewController:anotherViewController];
When anotherViewController is displayed the back button in the navigation bar will have @"yourImage.png"
and the default back button style (rectangular arrow). Also note it's fine to pass nil
as target
. The button behaves like the back button.
I too have been having problems with customView
on a navigationItem.backBarButtonItem
. I suspect it's probably just b0rked in the SDK.
While the workarounds outlined here do work, I've come up with a solution which is a little more elegant: it still uses navigationItem.leftBarButtonItem
, but takes care of it for you automagically (no more need for 'child' view controllers to need to know anything about their 'parent' and/or to manually set navigationItem.leftBarButtonItem
).
First up, have some class be a UINavigationControllerDelegate
for the UINavigationController
whose back button you're interested in. Then, in this class, set up something like the following willShowViewController
delegate method:
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
// reference to view controller stack
NSArray *viewControllers = [ navigationController viewControllers ];
if( [ viewControllers count ] > 1 ){
// the view controller we'll be linking to
UIViewController *backViewController = [ viewControllers objectAtIndex: [ viewControllers count ] - 2 ];
// create custom UIBarButtonItem
UIBarButtonItem *leftButton = [[ UIBarButtonItem alloc ] initWithCustomView: someCustomView ];
// set it as the leftBarButtonItem on the incoming viewcontroller
viewController.navigationItem.leftBarButtonItem = leftButton;
// tidy up
[ leftButton release ];
}
}
I had some further problems with this; it seems that UIBarButtonItem.action
and UIBarButtonItem.target
don't work when it's a navigationItem.leftBarButtonItem
. So, you're left with a custom back button that doesn't actually go back. I'll leave responding to touches in your custom view as an exercise for the reader (I used a UIButton), but you'll need add this method to your delegate class:
-(void)onDummyBackButtonTapped{
[ someNavigationController popViewControllerAnimated: YES ];
}
and hook it up to fire when your custom view is tapped.
I would be willing to bet that this is a bug on Apple's part as I am running into the exact same problem. The reason being is that I can get a custom UIBarButtonItem
to appear, but only when I don't try to use the initWithCustomView:
method. Per the API, the navigation controller checks the following things when a new view controller is pushed:
My case (as well as yours) is 2. I specify code exactly the same as yours (i.e., creating a UIButton
, setting its image
properties for various states, creating a UIBarButtonItem
, initializing it with the UIButton
, then setting my current view controller's backBarButtonItem
property to the UIBarButtonItem
); however, when I later push my view controller, nothing at all is displayed on the left-hand side of my navigation controller. Strangely, I can click where the "Back" button should be, and it pops the view controller.
Interestingly, if I create a UIBarButtonItem
using the initWithTitle:style:target:action:
method instead of the initWithCustomView:
method, it does show a custom button with a custom title. Also, as Travis mentioned, using the leftBarButtonItem
property instead works just fine. I'd rather adhere to the sanctioned logic, however, by specifying the "Back" button for the current view controller -- to be displayed later when a new view controller is pushed -- instead of creating a left button for the next view controller, which, arguably, should have no concern for anything pertaining to the view controller that came before it. :-\