iOS 7 custom back button
Asked Answered
M

12

38

I want to use custom back button. in iOS 6 everything is perfect but iOS 7 is strange.

[[UIBarButtonItem appearance] setBackButtonBackgroundImage:[[UIImage imageNamed:@"back_button_normal"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 12.0, 0, 12.0)] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];

first, it has no iOS 7 arrow and no background image.

(Russian locale)

initial state

then, if you press the button background image appears. Also i had background image set for UIControlStateHighlighted state and when you hold the button pressed highlighted image appears too. After any back button once pressed all back buttons have background image.

once pressed

BUT! If you present modal view controller, dismiss it, then push any view controller - iOS 7 arrow will appear at every back button.

I use DP5. Is that a UIKit bug?

PS Also i tried to create back button manually, using UIBarButtonItem, set background image to it, then self.navigationItem.backBarButtonItem = barButtonItem; Did not help. Then i tried to set background image to disabled state and change enabled property of my bar button item, did not help too.

enter image description here

Memling answered 16/9, 2013 at 9:42 Comment(3)
iOS 7 is still under NDA... You should look at UINavigationBar new properties introduced with iOS 7, there might be a solution for you thereAccursed
see this thread: #18099573Marcheshvan
I am facing the same issue dude. Did you get any solution for this?Hewes
C
50

This is not a bug, this how Back button looks in iOS 7. For example:

enter image description here

You should probably use the new concept for your application, and not to set background image for back button in iOS 7.

If you still want you back button have the same as it looked in iOS6 than you should probably create those back buttons manually:

- (void)loadView
{
    [super loadView];

    UIButton *backButton = [[UIButton alloc] initWithFrame: CGRectMake(0, 0, 60.0f, 30.0f)];
    UIImage *backImage = [[UIImage imageNamed:@"back_button_normal.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 12.0f, 0, 12.0f)];
    [backButton setBackgroundImage:backImage  forState:UIControlStateNormal];
    [backButton setTitle:@"Back" forState:UIControlStateNormal];
    [backButton addTarget:self action:@selector(popBack) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    self.navigationItem.leftBarButtonItem = backButtonItem;
}

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

Edit: Not to break Swipe Gesture (Here is a source)

self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
Cableway answered 16/9, 2013 at 9:56 Comment(4)
Thanks for reply. Unfortunately it is too expensive to manage back button using leftBarButtonItem in existing project, so we decided to follow the guideline and remove background images for bar buttons in iOS 7. Looks really nice.Memling
It is nice decision to move forward to the new iOS7 conceptCableway
How to hide the back arrow?Prieto
Too extensive. You don't need to init itwith custom button. Things can be done easier by customizing appearance of this single BarButtonItem to look like good ol' back button.Resolve
F
22

The custom background image not appearing on the first push was fixed in iOS 7 GM.

To hide standard back indicator use this code:

if ([UINavigationBar instancesRespondToSelector:@selector(setBackIndicatorImage:)]) { // iOS 7
    [navigationBarAppearance setBackIndicatorImage:[UIImage imageNamed:@"transparent_1px"]];
    [navigationBarAppearance setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"transparent_1px"]];
}
Fluor answered 1/10, 2013 at 11:11 Comment(0)
A
13

The custom background image not appearing initially was not fixed in iOS7 GM or final, as far as I can tell. I see the same problem. It does seem to be an Apple bug; the private view Apple uses simply does not get a setNeedsDisplay call when it needs it on initial display. Doing anything to it which causes that call should fix it -- like pressing on it (which likely changes internal state so it calls setNeedsDisplay on itself), or bringing a modal up (which probably forces a redisplay of the entire view hierarchy on the next viewWillAppear: call).

Using leftBarItems instead also can work, but that may cause a lot of maintenance issues with existing code (some screens may have their own left items, expecting that when set back to nil they restore the original back item, for example).

As mentioned, ideally you would be able to change to a borderless look on iOS7, which means that the bug isn't really apparent (since there is no background image). For some iOS6/iOS7 transition situations though, that may be difficult (lots of screens, and/or the need to support older iOS versions for a while and too hard to have two looks implemented, and it doesn't look good borderless without other changes). If that's the case, the following patch should work:

#import <objc/runtime.h>

@implementation UINavigationBar (BackButtonDisplayFix)

+ (void)load
{
    if ([UIDevice currentDevice].systemVersion.intValue >= 7)
    {
        /*
         * We first try to simply add an override version of didAddSubview: to the class.  If it
         * fails, that means that the class already has its own override implementation of the method
         * (which we are expecting in this case), so use a method-swap version instead.
         */
        Method didAddMethod = class_getInstanceMethod(self, @selector(_displaybugfixsuper_didAddSubview:));
        if (!class_addMethod(self, @selector(didAddSubview:),
                             method_getImplementation(didAddMethod),
                             method_getTypeEncoding(didAddMethod)))
        {
            Method existMethod = class_getInstanceMethod(self, @selector(didAddSubview:));
            Method replacement = class_getInstanceMethod(self, @selector(_displaybugfix_didAddSubview:));
            method_exchangeImplementations(existMethod, replacement);
        }
    }
}

- (void)_displaybugfixsuper_didAddSubview:(UIView *)subview
{
    [super didAddSubview:subview];
    [subview setNeedsDisplay];
}

- (void)_displaybugfix_didAddSubview:(UIView *)subview
{
    [self _displaybugfix_didAddSubview:subview]; // calls the existing method
    [subview setNeedsDisplay];
}

@end

Note: UINavigationBar does currently have an override of the method in question, so I'd expect the method_exchangeImplementations style to be used. I just added the other stuff for safety in case Apple changes their code. We may go borderless ourselves, but I did find this approach worked as an option (until a more thorough UI uplift), at least.

Additional note: This bug appears to be fixed in iOS 7.1. So, the patch could be conditionalized to only install the methods if running >= 7.0 and < 7.1.

Anticipation answered 18/10, 2013 at 14:53 Comment(1)
Thanks! I use this by subclassing the UINavigationBar and overriding the didAddSubview.Acima
K
6

There is a better solution that doesn't involve method swizzling.

You need to add UINavigationViewControllerDelegate method somewhere in your app.

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
dispatch_async(dispatch_get_main_queue(), ^{
    [[navigationController.navigationBar subviews] makeObjectsPerformSelector:@selector(setNeedsDisplay)];
});

}

Kerouac answered 28/1, 2014 at 7:24 Comment(0)
M
3

My solution is for iOS 7 and above.

At first, make default back button invisible.

self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];

then, set default backIndicatorImage of back button using custom image.

[UINavigationBar appearance].backIndicatorImage = [[UIImage imageNamed:@"topbar_icon_back_n.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[UINavigationBar appearance].backIndicatorTransitionMaskImage = [[UIImage imageNamed:@"topbar_icon_back_p.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

At this point, make custom UINavigationBar for resizing _UINavigationBarBackIndicatorView which contains above backIndicatorImage.

const CGPoint SANavigationBarOffset = {-8, 11.5};

@implementation SANavigationBar

- (void)layoutSubviews
{
    [super layoutSubviews];

    // set back button position
    NSArray *classNamesToReposition = @[@"_UINavigationBarBackIndicatorView"];

    for (UIView *view in [self subviews]) {
        if ([classNamesToReposition containsObject:NSStringFromClass([view class])]) {
            CGRect frame = [view frame];
            frame.origin.x = 0;
            frame.origin.y = 0;

            [view setFrame:frame];
        }
    }
}

@end

then, set it as my navigationBar

// set custom NavagationBar for back button position
[self.navigationController setValue:[[SANavigationBar alloc] init] forKey:@"navigationBar"];
Maurilla answered 15/1, 2015 at 9:16 Comment(1)
Thanks a lot! The first two steps are enough to change the default arrow image and hide the back button text.Errhine
T
1

Add button as navigation item in ios7 as below

 UIButton *btnAdd = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 60, 30)];

        [btnAdd setContentMode:UIViewContentModeScaleAspectFit];

        [btnAdd setBackgroundImage:[UIImage imageNamed:@"back.png"] forState:UIControlStateNormal];

        [btnAdd addTarget:self action:@selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside];

        UIBarButtonItem *btnAdd = [[UIBarButtonItem alloc] initWithCustomView:imView];

        self.navigationItem.rightBarButtonItem = btnAdd;
Tranquillize answered 23/10, 2013 at 12:48 Comment(0)
B
1

Using Swift you can just add a extension:

extension UIViewController: UIGestureRecognizerDelegate {
    func popBack() {
        self.navigationController?.popViewControllerAnimated(true)
    }

    func enableCustomBackButtom() {
        self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon-back"), style: UIBarButtonItemStyle.Plain, target: self, action:"popBack")

        self.navigationController?.interactivePopGestureRecognizer.delegate = self
    }
}

And in your UIViewController use like this:

self.enableCustomBackButtom()
Butyraceous answered 10/8, 2015 at 4:41 Comment(0)
W
0

I just did it providing the same behaviour as in iOS6 (notice that navigationBar is the UINavigationBar), make sure that navigationBar has a topItem

UINavigationItem *topItemNavigation = [navigationBar topItem];

UIBarButtonItem *barButtonTopItemNavigation = [[UIBarButtonItem alloc] initWithTitle:topItemNavigation.title style:UIBarButtonItemStyleBordered target:nil action:nil];

[barButtonTopItemNavigation setBackButtonBackgroundImage:YOUR_IMAGE_BACKGROUND forState:UIControlStateNormal barMetrics:UIBarMetricsDefault ];
            [topItemNavigation setBackBarButtonItem: barButtonTopItemNavigation];
        }
Wichita answered 4/2, 2014 at 19:4 Comment(0)
S
0

My solution was to write a category on UINavigationItem. This is for iOS7.

- (void)mdSetCustomBackButton:(UINavigationController *)navigationController
{
    MDBackButton *backButton = [[MDBackButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 44.0, 44.0) navigationController:navigationController];
    [backButton addTarget:self action:@selector(popBack:) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    [self setLeftBarButtonItem:barButtonItem];
    [navigationController.interactivePopGestureRecognizer setDelegate:(id<UIGestureRecognizerDelegate>)self];
}

- (void)popBack:(MDBackButton *)sender
{
    [sender.navigationController popViewControllerAnimated:YES];
}

And subclass UIButton to add a UINavigationController property (to pop and set swipe back delegate).

@property (nonatomic, weak) UINavigationController *navigationController;

@implementation MDBackButton

- (id)initWithFrame:(CGRect)frame navigationController:(UINavigationController *)navigationController
{
    self = [super initWithFrame:frame];
    if(self){
        _navigationController = navigationController;
        [self setImage:[UIImage imageNamed:@"back_button"] forState:UIControlStateNormal];
    }
    return self;
}
Shop answered 20/2, 2014 at 9:36 Comment(0)
V
0

This is work for me:

- (void)setCustomNavigationBackButton
{    
  self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];

  UIImage *myIcon = [self imageWithImage:[UIImage imageNamed:@"backbutton.png"] scaledToSize:CGSizeMake(20, 20)];

  self.navigationController.navigationBar.backIndicatorImage = myIcon;
  self.navigationController.navigationBar.backIndicatorTransitionMaskImage = myIcon;
}

- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize 
{
  //UIGraphicsBeginImageContext(newSize);
  // In next line, pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution).
  // Pass 1.0 to force exact pixel size.
  UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
  [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
  UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
  return newImage;
}

Also, custom font with custom color:

//self.navigationController.navigationBar.tintColor = [UIColor whiteColor];

[[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil] setTitleTextAttributes:
 @{NSForegroundColorAttributeName:[UIColor whiteColor],
   NSFontAttributeName:[UIFont fontWithName:@"Signika-Bold" size:20]}

forState:UIControlStateNormal];

Reference: https://mcmap.net/q/80489/-the-simplest-way-to-resize-an-uiimage

Viglione answered 20/5, 2015 at 15:17 Comment(0)
S
0

I use these codes below, which works in iOS 8

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.translatesAutoresizingMaskIntoConstraints = NO;
button.exclusiveTouch = YES;
button.titleLabel.font = [UIFont systemFontOfSize:14.0];
[button setTitleColor:kWhiteColor forState:UIControlStateNormal];
[button setTitleColor:[UIColor colorWithRed:1/255.0 green:36/255.0 blue:60/255.0 alpha:1.0] forState:UIControlStateHighlighted];
[button setTitle:@"Back" forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:@"barbutton_back"] forState:UIControlStateNormal];
[button setImageEdgeInsets:UIEdgeInsetsMake(1.0, 0.0, 0.0, 0.0)];
CGSize fontSize = [button.titleLabel sizeThatFits:CGSizeMake(100.0, 30.0)];
button.frame = CGRectMake(0.0, 0.0, button.imageView.image.size.width+fontSize.width, 30.0);
UIBarButtonItem *barbtn = [[UIBarButtonItem alloc] initWithCustomView:button];
//fix iOS 7 left margin
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negativeSpacer.width = -10;
self.navigationItem.leftBarButtonItems = [NSArray arrayWithObjects:negativeSpacer,barbtn, nil];
Semiporcelain answered 17/10, 2015 at 12:2 Comment(0)
F
-1
-(void) viewWillAppear:(BOOL)animated
{
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    [btn setFrame:CGRectMake(0, 0, 30, 44)];
    [btn setImage:[UIImage imageNamed:@"btnBack.png"] forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(PopToView) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *btnBack = [[UIBarButtonItem alloc] initWithCustomView:btn];
    [btnBack setTintColor:[UIColor whiteColor]];
    [[self.navigationController navigationItem] setLeftBarButtonItem:btnBack];

}
Flibbertigibbet answered 20/5, 2014 at 6:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.