Trying to handle "back" navigation button action in iOS
Asked Answered
D

11

76

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

Donohoe answered 16/9, 2013 at 9:6 Comment(5)
would it be good enough to place the code you need in viewWillDisappear ?Benita
Use the methods on UINavigationControllerDelegate.Corbicula
@Smick No, unfortunately that won't be enough in my scenario...Donohoe
@MikeWeller I tried but I couldn't make it workDonohoe
Check out the answer in this question. Best solution I've found. #1215465Welcher
C
131

Try this code using VIewWillDisappear method to detect the press of The back button of NavigationItem:

-(void) viewWillDisappear:(BOOL)animated
{
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) 
    {
        // Navigation button was pressed. Do some stuff 
        [self.navigationController popViewControllerAnimated:NO];
    }
    [super viewWillDisappear:animated];
}

OR There is another way to get Action of the Navigation BAck button.

Create Custom button for UINavigationItem of back button .

For Ex:

In ViewDidLoad :

- (void)viewDidLoad 
{
    [super viewDidLoad];
    UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:@"Home" style:UIBarButtonItemStyleBordered target:self action:@selector(home:)];
    self.navigationItem.leftBarButtonItem=newBackButton;
}

-(void)home:(UIBarButtonItem *)sender 
{
    [self.navigationController popToRootViewControllerAnimated:YES];
}

Swift :

override func willMoveToParentViewController(parent: UIViewController?) 
{
    if parent == nil 
    {
        // Back btn Event handler
    }
}
Crankpin answered 16/9, 2013 at 9:12 Comment(4)
I don't think you need [self.navigationController popViewControllerAnimated:NO] in viewWillDisappear.Hora
I think this method does not work anymore with iOS 8 as self.navigationController.viewControllers contains only one element, == selfTradesman
@Sébastien Stormacq Why you say that? It work in iOS 8.Oshiro
If you call this in ViewWillDisappear, it won't be called just when back button is tapped. It will be called whenever the VC is being popped or whenever a new VC is being pushedKronos
S
41

Swift

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        //"Back pressed"
    }
}
Sharleensharlene answered 16/12, 2014 at 12:59 Comment(1)
Only problem with this solution, is if you swipe to go back, and change your mind, this will get triggered.Handcar
A
18

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.

Apotropaic answered 30/1, 2014 at 16:32 Comment(0)
S
12

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 {
        // ...  
    }
}
Solicitor answered 6/8, 2015 at 10:51 Comment(0)
M
6

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

Objective-C

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}
Moonier answered 14/7, 2015 at 16:54 Comment(0)
F
3

Set the UINavigationBar's delegate, and then use:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
    //handle the action here
}
Fricke answered 24/7, 2014 at 6:28 Comment(2)
If you are using a UINavigationController to manage the navigation bar then trying to set the delegate causes an exception: "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot manually set the delegate on a UINavigationBar managed by a controller.'". The UINavigationController is the delegate. Which means you can sub-class the controller and override the UINavigationBarDelegate methods (probably calling super).Stanwin
But you can't directly call super because UINavigationController doesn't publicly conform to UINavigationBarDelegate, resulting in a compiler error! There might be a solution using UINavigationControllerDelegate.Stanwin
H
3

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
}
Heading answered 5/12, 2018 at 23:18 Comment(1)
Can you expand on your answer and show how exactly to use it in a view controller.Scheffler
F
3

In Swift 4 or above:

override func didMove(toParent parent: UIViewController?) {
    if parent == nil {
        //"Back pressed"
    }
}
Fourgon answered 10/5, 2019 at 10:6 Comment(0)
A
2

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
    }
}
Anisotropic answered 26/8, 2015 at 15:23 Comment(0)
A
1

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.

Antilebanon answered 20/12, 2018 at 14:14 Comment(0)
H
0

When using iOS 16 or newer, you can intercept the action of the back button by setting the backAction property of the navigationItem of the view controller. Example:

navigationItem.backAction = UIAction(handler: { [weak self] action in
    
    // Your custom action here
    self?.myCustomAction()

    // Navigate one view controller back
    self?.navigationController?.popViewController(animated: true)
})
Hooves answered 7/6 at 12:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.