[ios] Trying to handle "back" navigation button action in iOS

I need to detect when the user taps the "back" button on the navigation bar, in order to perform some operations when that occurs. I'm trying to set manually an action to such button, this way:

[self.navigationItem.backBarButtonItem setAction:@selector(performBackNavigation:)];

- (void)performBackNavigation:(id)sender
{
   // Do operations

   [self.navigationController popViewControllerAnimated:NO];
}

I firstly placed that code in the view controller itself, but I found that self.navigationItem.backBarButtonItem seemed to be nil, so I moved that same code to the parent view controller, which pushes the former to the navigation stack. But I'm neither able to make it work. I've read some posts regarding this issue, and some of them said that the selector needs to be set at the parent view controller, but for me it doesn't work anyway... What could I'm doing wrong?

Thanks

This question is related to ios uinavigationcontroller uinavigationbar selector

The answer is


Set the UINavigationControllerDelegate and implement this delegate func (Swift):

func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
    if viewController is <target class> {
        //if the only way to get back - back button was pressed
    }
}

In Swift 4 or above:

override func didMove(toParent parent: UIViewController?) {
    if parent == nil {
        //"Back pressed"
    }
}

Swift

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        //"Back pressed"
    }
}

This is Objective-C version of dadachi's Answer :

Objective-C

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

None of the other solutions worked for me, but this does:

Create your own subclass of UINavigationController, make it implement the UINavigationBarDelegate (no need to manually set the navigation bar's delegate), add a UIViewController extension that defines a method to be called on a back button press, and then implement this method in your UINavigationController subclass:

func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
    self.topViewController?.methodToBeCalledOnBackButtonPress()
    self.popViewController(animated: true)
    return true
}

Perhaps this answers doesn't fit your explanation but question title. It's useful when you are trying to know when you tapped the back button on an UINavigationBar.

In this case you can use UINavigationBarDelegate protocol and implement one of this methods:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item;

When didPopItem method is called, it's because you either tapped the back button or you used [UINavigationBar popNavigationItemAnimated:] method and the navigation bar did pop the item.

Now, if you want to know what action triggered the didPopItem method you can use a flag.

With this approach I don't need to manually add a left bar button item with an arrow image in order to make it similar to iOS back button, and be able to set my custom target/action.


Let's see an example:

I have a view controller that has a page view controller, and a custom page indicator view. I'm also using a custom UINavigationBar to display a title to know on what page am I and the back button to go back to the previous page. And I also can swipe to previous/next page on page controller.

#pragma mark - UIPageViewController Delegate Methods
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {

    if( completed ) {

        //...

        if( currentIndex > lastIndex ) {

            UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:@"Some page title"];

            [[_someViewController navigationBar] pushNavigationItem:navigationItem animated:YES];
            [[_someViewController pageControl] setCurrentPage:currentIndex];
        } else {
            _autoPop = YES; //We pop the item automatically from code.
            [[_someViewController navigationBar] popNavigationItemAnimated:YES];
            [[_someViewController pageControl] setCurrentPage:currentIndex];
        }
    }

}

So then I implement UINavigationBar delegate methods:

#pragma mark - UINavigationBar Delegate Methods
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
    if( !_autoPop ) {
        //Pop by back button tap
    } else {
        //Pop from code
    }

    _autoPop = NO;

    return YES;
}

In this case I used shouldPopItem because the pop is animated and I wanted to handle the back button immediately and not to wait until transition is finished.


Use a custom UINavigationController subclass, which implements the shouldPop method.

In Swift:

class NavigationController: UINavigationController, UINavigationBarDelegate
{
    var shouldPopHandler: (() -> Bool)?

    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool
    {
        if let shouldPopHandler = self.shouldPopHandler, !shouldPopHandler()
        {
            return false
        }
        self.popViewController(animated: true) // Needed!
        return true
    }
}

When set, your shouldPopHandler() will be called to decide whether the controller will be pop or not. When not set it will just get popped as usual.

It is a good idea to disable UINavigationControllers interactivePopGestureRecognizer as the gesture won't call your handler otherwise.


The problem with didMoveToParentViewController it's that it gets called once the parent view is fully visible again so if you need to perform some tasks before that, it won't work.

And it doesn't work with the driven animation gesture. Using willMoveToParentViewController works better.

Objective-c

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        // ...
    }
}

Swift

override func willMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        // ...  
    }
}

Set the UINavigationBar's delegate, and then use:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
    //handle the action here
}

Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to uinavigationcontroller

How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? How to hide a navigation bar from first ViewController in Swift? Execute action when back bar button of UINavigationController is pressed Programmatically navigate to another view controller/scene How to force view controller orientation in iOS 8? presenting ViewController with NavigationViewController swift How to Navigate from one View Controller to another using Swift UINavigationBar Hide back Button Text How to check if a view controller is presented modally or pushed on a navigation stack? Trying to handle "back" navigation button action in iOS

Examples related to uinavigationbar

Swift programmatically navigate to another view controller/scene How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? How to hide a navigation bar from first ViewController in Swift? Change color of Back button in navigation bar NavigationBar bar, tint, and title text color in iOS 8 transparent navigation bar ios presenting ViewController with NavigationViewController swift Changing navigation title programmatically Navigation bar with UIImage for title Changing navigation bar color in Swift

Examples related to selector

How to use addTarget method in swift 3 What is the Swift equivalent of respondsToSelector? @selector() in Swift? Trying to handle "back" navigation button action in iOS jQuery select child element by class with unknown path android button selector How do I select a sibling element using jQuery? Passing parameters on button action:@selector "unrecognized selector sent to instance" error in Objective-C Objective-C: Calling selectors with multiple arguments