5

Problem : I would like to custom the navigation back button title in the popped view controller like Whatsapp ( < Chats (2) / < Chats (3) ).

However to assign a new backBarButtonItem in the popped view controller will disable the swipe back gesture, if you use

self.navigationController.interactivePopGestureRecognizer.delegate = self;

to keep the gesture work, it will give you more troublessss (too many bugssss).

Rex Lam
  • 1,385
  • 1
  • 15
  • 23
  • Refer to this http://stackoverflow.com/a/9871741/1707115 – Kaiusee May 20 '14 at 17:55
  • I wrote a swift extension so you can just add that file and use the default UINavigationController and it should work. https://gist.github.com/donpironet/f5ce9857f422df46d52f053c22d96c8c – user1007522 Jun 29 '16 at 10:58

4 Answers4

2

You have to set the self.navigationItem.backBarButtonItem property on the ViewController that comes before the one will show the title.

In the Whatsapp example, you will have to set the title on the chats list view controller.

Something like that:

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

After that, you can set just the title of self.navigationItem.backBarButtonItem.

Gonzo
  • 1,533
  • 16
  • 28
  • 1
    I tried it before, the self.navigationItem.backBarButtonItem is a nil object. – Rex Lam May 20 '14 at 18:01
  • Yeah, you were right. I've edited de answer, now should work! – Gonzo May 20 '14 at 18:14
  • No... it doesn't work, if you assign the backBarButtonItem in first view controller, after push to second view controller the backBarButtonItem will become nil again, even you assign it at prepareForSegue method or just like my answer in a custom navigation controller. That's why I made it as a public property. – Rex Lam May 20 '14 at 18:24
  • Just to be sure, you're setting the button on the first viewcontroller. But you are setting the button on `self`, not in `segue.destinationViewController`, right? – Gonzo May 20 '14 at 18:40
  • `viewController.navigationItem.backBarButtonItem = backButton;` It should be destination view controller. – Rex Lam May 20 '14 at 19:11
  • At first might look counterintuitive, but this `viewController` must be the self of `firstViewController`. – Gonzo May 20 '14 at 19:13
  • Just checked, it is destination view controller. Yes, it looks counterintuitive, I don't understand this behavior too – Rex Lam May 20 '14 at 19:18
1

If you want to empty back button title in whole of application, one of the solutions is to swizzle viewDidLoad and empty back button title in the swizzled viewDidLoad. And it won't affect to interactivePopGestureRecognizer's work (make sure interactivePopGestureRecognizer is enabled).

            @implementation UIViewController (Customizations)

            + (void)load {
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    [UIViewController swizzleClass:[UIViewController class] method:@"viewDidLoad"];
                });
            }

            + (void)swizzleClass:(Class)class method:(NSString*)methodName {
                SEL originalMethod = NSSelectorFromString(methodName);
                SEL newMethod = NSSelectorFromString([NSString stringWithFormat:@"%@%@", @"override_", methodName]);
                [self swizzle:class from:originalMethod to:newMethod];
            }

            + (void)swizzle:(Class)class from:(SEL)original to:(SEL)new {
                Method originalMethod = class_getInstanceMethod(class, original);
                Method newMethod = class_getInstanceMethod(class, new);
                if(class_addMethod(class, original, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
                    class_replaceMethod(class, new, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
                } else {
                    method_exchangeImplementations(originalMethod, newMethod);
                }
            }

            - (void)override_viewDidLoad {
                //Empty back button title
                UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
                [self.navigationItem setBackBarButtonItem:backButtonItem];
                [self override_viewDidLoad];
            }

            @end
Narek Safaryan
  • 140
  • 1
  • 7
0

For people who just want to use the UINavigationController and have this fixed. I wrote a Extension for the UINavigationController in Swift.

It doesn't have the problem that the stack can be broken if you swipe back to soon.

extension UINavigationController: UINavigationControllerDelegate, UIGestureRecognizerDelegate {

    public override static func initialize() {
        struct Static {
            static var token: dispatch_once_t = 0
        }

        if self !== UINavigationController.self {
            return
        }

        dispatch_once(&Static.token) {

            // Swizzle viewDidLoad

            self.swizzleViewDidLoad()

            // Swizzle pushViewController

            self.swizzlePushController()
        }
    }

    // MARK: - Helpers

    static func swizzleViewDidLoad() {
        let originalViewDidLoadSelector = #selector(UINavigationController.viewDidLoad)
        let swizzledViewDidLoadSelector = #selector(UINavigationController.newViewDidLoad)

        let originalViewDidLoadMethod = class_getInstanceMethod(self, originalViewDidLoadSelector)
        let swizzledViewDidLoadMethod = class_getInstanceMethod(self, swizzledViewDidLoadSelector)

        let didAddViewDidLoadMethod = class_addMethod(self, originalViewDidLoadSelector, method_getImplementation(swizzledViewDidLoadMethod), method_getTypeEncoding(swizzledViewDidLoadMethod))

        if didAddViewDidLoadMethod {
            class_replaceMethod(self, swizzledViewDidLoadSelector, method_getImplementation(originalViewDidLoadMethod), method_getTypeEncoding(swizzledViewDidLoadMethod))
        } else {
            method_exchangeImplementations(originalViewDidLoadMethod, swizzledViewDidLoadMethod);
        }
    }

    static func swizzlePushController() {
        let originalPushControllerSelector = #selector(UINavigationController.pushViewController(_:animated:))
        let swizzledPushControllerSelector = #selector(UINavigationController.newPushViewController(_:animated:))

        let originalPushControllerMethod = class_getInstanceMethod(self, originalPushControllerSelector)
        let swizzledPushControllerMethod = class_getInstanceMethod(self, swizzledPushControllerSelector)

        let didAddPushControllerMethod = class_addMethod(self, originalPushControllerSelector, method_getImplementation(swizzledPushControllerMethod), method_getTypeEncoding(swizzledPushControllerMethod))

        if didAddPushControllerMethod {
            class_replaceMethod(self, swizzledPushControllerSelector, method_getImplementation(originalPushControllerMethod), method_getTypeEncoding(swizzledPushControllerMethod))
        } else {
            method_exchangeImplementations(originalPushControllerMethod, swizzledPushControllerMethod);
        }
    }

    // MARK: - Method Swizzling

    func newViewDidLoad() {
        self.newViewDidLoad()

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

    func newPushViewController(viewController: UIViewController, animated: Bool) {
        self.interactivePopGestureRecognizer?.enabled = false

        self.newPushViewController(viewController, animated: animated)
    }

    // MARK: - UINavigationControllerDelegate

    public func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
        self.interactivePopGestureRecognizer?.enabled = true
    }
}
user1007522
  • 7,858
  • 17
  • 69
  • 113
-1

Answer:

After spent a day for this issue, I have a quite simple and easy solution, works on both iOS 6 & iOS 7 :

1). Custom style (color, font) in AppDelegate (Assume you will use the same style for all controllers)

2). Create a custom UINavigationController like this :

CustomBackNavigationController.h

@interface CustomBackNavigationController : UINavigationController <UINavigationControllerDelegate>

@property (nonatomic, strong) UIBarButtonItem *backButton;

@end

CustomBackNavigationController.m

@implementation CustomBackNavigationController

@synthesize backButton;

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.delegate = self;
}


- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    backButton = [[UIBarButtonItem alloc] initWithTitle:@"Chats" style:UIBarButtonItemStyleBordered target:nil action:nil];
    viewController.navigationItem.backBarButtonItem = backButton;
}

@end

in the popped view controllers, just change the backButton title like this

- (void)someMethod
{
    CustomBackNavigationController *customBackNavigationController = (CustomBackNavigationController *) self.navigationController;

    [customBackNavigationController.backButton setTitle:@"Chats (1)"];
}

That's it !

Rex Lam
  • 1,385
  • 1
  • 15
  • 23