18

For an application I'm developing, I need to display a custom back button in a navigation bar. I have the button asset as a PNG image, and I'm writing this code:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
    backButton.frame = CGRectMake(0, 0, 79, 29.0);
    [backButton setImage:[UIImage imageNamed:@"button_back.png"] forState:UIControlStateNormal];
    self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:backButton] autorelease];

}

When I push this view controller, the custom button does not show up, and instead I get the standard back button with the title of this view controller inside.

Things I already tried:

  1. Doubled check that the button backButton is created properly, by adding it to the view hierarchy. It displays properly.
  2. In the same method, changed the title property of the navigationItem and confirmed that it changes (as expected) the content of my back button.

Can anyone spot what I'm doing wrong? Did anyone succeed in using a custom image as the back button on with a UINavigationController?

pgb
  • 24,813
  • 12
  • 83
  • 113

10 Answers10

18

Starting with iOS 5, this is simple:

[[UIBarButtonItem appearance]
            setBackButtonBackgroundImage:[UIImage imageNamed:@"back_button.png"]
            forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];

You can place that in your app delegate and it will set the background image to all back buttons in the app (for that control state and bar metrics, of course).

pgb
  • 24,813
  • 12
  • 83
  • 113
Matías R
  • 2,195
  • 1
  • 14
  • 12
13

I'm reposting my solution from https://stackoverflow.com/a/16831482/171933:

I create a simple category on UIViewController:

UIViewController+ImageBackButton.h

#import <UIKit/UIKit.h>

@interface UIViewController (ImageBackButton)

- (void)setUpImageBackButton;

@end

UIViewController+ImageBackButton.m

#import "UIViewController+ImageBackButton.h"

@implementation UIViewController (ImageBackButton)

- (void)setUpImageBackButton
{
    UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 34, 26)];
    [backButton setBackgroundImage:[UIImage imageNamed:@"back_arrow.png"] forState:UIControlStateNormal];
    UIBarButtonItem *barBackButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    [backButton addTarget:self action:@selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
    self.navigationItem.leftBarButtonItem = barBackButtonItem;
    self.navigationItem.hidesBackButton = YES;
}

- (void)popCurrentViewController
{
    [self.navigationController popViewControllerAnimated:YES];
}

@end

Now all you have to do is #import UIViewController+ImageBackButton.h in either all of your view controllers or in a custom base view controller class that your other view controllers inherit from and implement the viewWillAppear: method:

- (void)viewWillAppear:(BOOL)animated
{
    [self setUpImageBackButton];
}

That's all. Now you have an image back button everywhere. Without a border. Enjoy!

Community
  • 1
  • 1
Johannes Fahrenkrug
  • 42,912
  • 19
  • 126
  • 165
  • [backButton addTarget:self.navigationController action:@selector(popViewControllerAnimated:) forControlEvents:UIControlEventTouchUpInside]; does not provide the 'animated' argument and is undefined. it's better just to add a method that explicitly provides the 'animated' argument – Or Arbel May 30 '13 at 10:48
  • @OrArbel Thank you, you're right. It works the way it is, but it is cleaner to explicitly call it. I've edited the code :) – Johannes Fahrenkrug May 30 '13 at 11:19
  • 1
    This is fine but will disable the swipe to go back if you hide the back button. So IMO you lose more than you gain. – AppHandwerker Aug 07 '15 at 13:51
  • One of the main problems of the approach is that back button hasn't extra space at the left edge of the screen, adding button to left bar button will add extra space which is not intractable. And it's hard to manipulate with bar button items in navigation item - every time you need to reassign left button instead of hiding, which don't give you ability to create superclass of ViewController and change the back buttons in one place, as a result - bad code design – HotJard Sep 04 '15 at 07:56
5

The backBarButtonItem property works as intended, but it will always add its standard button shape and color based on the navigation bar tint color.

You can customize the text, but not replace the image.

One workaround, as Andrew Pouliot suggested, is to use leftBarButtonItem instead, but I stuck to the standard button instead.

explodes
  • 1,182
  • 2
  • 9
  • 14
pgb
  • 24,813
  • 12
  • 83
  • 113
4

Isn't the simplest solution just to design it from your storyboard with whatever image or colors you want, and just drag a new action to your controller?

Swift Code

@IBAction func backButton(sender: AnyObject) {
    self.navigationController?.popViewControllerAnimated(true)
}
vladCovaliov
  • 4,333
  • 2
  • 43
  • 58
  • 4
    SO is so great! I asked this question in 2010, and I get an answer now with a Swift sample code, and suggesting the use of Storyboards :) – pgb Sep 24 '14 at 13:05
3

Confusingly backBarButtonItem is not what you're looking for.

It just controls the title on the back button for the next view controller. What you want is to set the leftBarButtonItem to your custom back button.

Andrew Pouliot
  • 5,423
  • 1
  • 30
  • 34
  • 3
    I think I do want `backBarButtonItem`. What I want to control is how the back button will look when the `UIViewController` gets pushed in the `UINavigationController` stack. Indeed, minor changes to the `backBarButtonItem` work as expected, it's just the custom view that is not sticking. – pgb Aug 17 '10 at 19:57
  • 1
    backBarButtonItem property works. Just have to set it at the right time. – SundayMonday Aug 25 '11 at 02:31
2

Johannes Fahrenkrug's Answer works, but the back image would appear at a very wired position.

Here I found a better way to position the image at the right place:

Make Sure You Have a back image with size 24x24(@1x) , I call it backImage

Execute the following code when your app Launch

UINavigationBar.appearance().barTintColor = nil
UINavigationBar.appearance().backIndicatorImage = backImage
UINavigationBar.appearance().backIndicatorTransitionMaskImage = backImage
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), forBarMetrics: .Default)
duan
  • 8,515
  • 3
  • 48
  • 70
  • I've setBackButtonTitlePositionAdjustment on didFinishLaunchingWithOptions and in some screens I can see the back button title animating off screen :/ – Cezar Signori Jul 12 '17 at 12:55
1

I do not think that ViewController itself should know anything about its back button According to OOP this is the responsibility of containerViewController in which your view controller is inserted, for example UINavigationController.

Subclass your NavigationController and overload in it superClass method like this:

@implementation STONavigationController

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [super pushViewController:viewController animated:animated];
    if ([self.viewControllers indexOfObject:viewController] != NSNotFound &&
        [self.viewControllers indexOfObject:viewController] > 0){
        UIImage *img = [UIImage imageNamed:@"back-1"];
        UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, img.size.width * 2, img.size.height * 2)];
        [backButton setBackgroundImage:img forState:UIControlStateNormal];
        UIBarButtonItem *barBackButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
        [backButton addTarget:self action:@selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
        viewController.navigationItem.leftBarButtonItem = barBackButtonItem;
        viewController.navigationItem.hidesBackButton   = YES;
    }
}

- (void)popCurrentViewController
{
    [self popViewControllerAnimated:YES];
}

@end 
Rich Tolley
  • 3,812
  • 1
  • 30
  • 39
Nikolay Shubenkov
  • 3,133
  • 1
  • 29
  • 31
1

See this answer here: How to create backBarButtomItem with custom view for a UINavigationController

You just need to set the backBarButtonItem property on the navigationController before pushing the viewController. Setting the backBarButtonItem property in the viewController's viewDidLoad method (for example) doesn't work.

Community
  • 1
  • 1
SundayMonday
  • 19,147
  • 29
  • 100
  • 154
0

As @pgb suggested 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
Ammar Mujeeb
  • 1,222
  • 18
  • 21
0

Simply create a UIBarButtonItem instead of an embedded UIButton in UIBarButtonItem. Works fine!

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"button_back.png"] style:UIBarButtonItemStyleBordered target:nil action:nil];

self.navigationItem.backBarButtonItem = backButton; 
Venk
  • 5,949
  • 9
  • 41
  • 52
romrom
  • 642
  • 1
  • 6
  • 14
  • 1
    This doesn't work. The back button background image is still shown (Unless the image you use is larger than the default back button image) – probablyCorey May 12 '11 at 01:15
  • http://stackoverflow.com/questions/526520/how-to-create-backbarbuttomitem-with-custom-view-for-a-uinavigationcontroller/7184426#7184426 – SundayMonday Aug 25 '11 at 02:32