Setting action for back button in navigation controller

Viewed 168453

I'm trying to overwrite the default action of the back button in a navigation controller. I've provided a target an action on the custom button. The odd thing is when assigning it though the backbutton attribute it doesn't pay attention to them and it just pops the current view and goes back to the root:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

As soon as I set it through the leftBarButtonItem on the navigationItem it calls my action, however then the button looks like a plain round one instead of the arrowed back one:

self.navigationItem.leftBarButtonItem = backButton;

How can I get it to call my custom action before going back to the root view? Is there a way to overwrite the default back action, or is there a method that is always called when leaving a view (viewDidUnload doesn't do that)?

29 Answers

Overriding navigationBar(_ navigationBar:shouldPop): This is not a good idea, even if it works. for me it generated random crashes on navigating back. I advise you to just override the back button by removing the default backButton from navigationItem and creating a custom back button like below:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

========================================

Building on previous responses with UIAlert in Swift5 in a Asynchronous way


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
      
        if viewControllers.count < navigationBar.items!.count {
            return true
        }
        
        // Check if we have a view controller that wants to respond to being popped
        
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            
            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}


On your controller


extension MyController: NavigationControllerBackButtonDelegate {
    
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {
    
        let msg = "message"
        
        /// show UIAlert
        alertAttention(msg: msg, actions: [
            
            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])
   
    }

}

I don't believe this is possible, easily. The only way I believe to get around this is to make your own back button arrow image to place up there. It was frustrating for me at first but I see why, for consistency's sake, it was left out.

You can get close (without the arrow) by creating a regular button and hiding the default back button:

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;

Swift 4 iOS 11.3 Version:

This builds on the answer from kgaidis from https://stackoverflow.com/a/34343418/4316579

I am not sure when the extension stopped working, but at the time of this writing (Swift 4), it appears that the extension will no longer be executed unless you declare UINavigationBarDelegate conformity as described below.

Hope this helps people that are wondering why their extension no longer works.

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

    }
}

By using the target and action variables that you are currently leaving 'nil', you should be able to wire your save-dialogs in so that they are called when the button is "selected". Watch out, this may get triggered at strange moments.

I agree mostly with Amagrammer, but I don't think it would be that hard to make the button with the arrow custom. I would just rename the back button, take a screen shot, photoshop the button size needed, and have that be the image on the top of your button.

Related