Checking if a UIViewController is about to get Popped from a navigation stack?

IphoneObjective CUikitUiviewcontrollerUinavigationbar

Iphone Problem Overview


I need to know when my view controller is about to get popped from a nav stack so I can perform an action.

I can't use -viewWillDisappear, because that gets called when the view controller is moved off screen for ANY reason (like a new view controller being pushed on top).

I specifically need to know when the controller is about to be popped itself.

Any ideas would be awesome, thanks in advance.

Iphone Solutions


Solution 1 - Iphone

Override the viewWillDisappear method in the presented VC, then check the isMovingFromParentViewController flag within the override and do specific logic. In my case I'm hiding the navigation controllers toolbar. Still requires that your presented VC understand that it was pushed though so not perfect.

Solution 2 - Iphone

Fortunately, by the time the viewWillDisappear method is called, the viewController has already been removed from the stack, so we know the viewController is popping because it's no longer in the self.navigationController.viewControllers

Swift 4

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if let nav = self.navigationController {
        let isPopping = !nav.viewControllers.contains(self)
        if isPopping {
            // popping off nav
        } else {
            // on nav, not popping off (pushing past, being presented over, etc.)
        }
    } else {
        // not on nav at all
    }
}

Original Code

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if ((self.navigationController) && 
        (![self.navigationController.viewControllers containsObject:self])) {
        NSLog(@"I've been popped!");
    }
}

Solution 3 - Iphone

Try overriding willMoveToParentViewController: (instead of viewWillDisappear:) in your custom subclass of UIViewController.

> Called just before the view controller is added or removed from a container view controller.

- (void)willMoveToParentViewController:(UIViewController *)parent
{
    [super willMoveToParentViewController:parent];
    if (!parent) {
        // `self` is about to get popped.
    }
}

Solution 4 - Iphone

I don't think there is an explicit message for this, but you could subclass the UINavigationController and override - popViewControllerAnimated (although I haven't tried this before myself).

Alternatively, if there are no other references to the view controller, could you add to its - dealloc?

Solution 5 - Iphone

This is working for me.

- (void)viewDidDisappear:(BOOL)animated
{
	if (self.parentViewController == nil) {
		NSLog(@"viewDidDisappear doesn't have parent so it's been popped");
		//release stuff here
	} else {
		NSLog(@"PersonViewController view just hidden");
	}
}

Solution 6 - Iphone

You can catch it here.

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

    if (viewController == YourAboutToAppearController) {
            // do something
    }
}

This will fire just before the display of the new View. Nobody's moved yet. I use all the time to do magic in front of the asinine NavigationController. You can set titles and button titles and do whatever there.

Solution 7 - Iphone

I have the same problem. I tried with viewDisDisappear, but I don't have the function get called :( (don't know why, maybe because all my VC is UITableViewController). The suggestion of Alex works fine but it fails if your Navigation controller is displayed under the More tab. In this case, all VCs of your nav controllers have the navigationController as UIMoreNavigationController, not the navigation controller you have subclassed, so you will not be notified by the nav when a VC is about to popped.
Finaly, I solved the problem with a category of UINavigationController, just rewrite - (UIViewController *)popViewControllerAnimated:(BOOL)animated

- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
   NSLog(@"UINavigationController(Magic)");
   UIViewController *vc = self.topViewController;
   if ([vc respondsToSelector:@selector(viewControllerWillBePopped)]) {
      [vc performSelector:@selector(viewControllerWillBePopped)];
   }
   NSArray *vcs = self.viewControllers;
   UIViewController *vcc = [vcs objectAtIndex:[vcs count] - 2];
   [self popToViewController:vcc animated:YES];
   return vcc;}

It works well for me :D

Solution 8 - Iphone

I tried this:

- (void) viewWillDisappear:(BOOL)animated {
	// If we are disappearing because we were removed from navigation stack
	if (self.navigationController == nil) {
		// YOUR CODE HERE
	}
	
	[super viewWillDisappear:animated];
}

The idea is that at popping, the view controller's navigationController is set to nil. So if the view was to disappear, and it longer has a navigationController, I concluded it was popped. (might not work in other scenarios).

Can't vouch that viewWillDisappear will be called upon popping, as it is not mentioned in the docs. I tried it when the view was top view, and below top view - and it worked in both.

Good luck, Oded.

Solution 9 - Iphone

Subclass UINavigationController and override popViewController:

Swift 3
protocol CanPreventPopProtocol {
    func shouldBePopped() -> Bool
}
  
class MyNavigationController: UINavigationController {
    override func popViewController(animated: Bool) -> UIViewController? {
        let viewController = self.topViewController
        
        if let canPreventPop = viewController as? CanPreventPopProtocol {
            if !canPreventPop.shouldBePopped() {
                return nil
            }
        }
        return super.popViewController(animated: animated)
    }

    //important to prevent UI thread from freezing
    //
    //if popViewController is called by gesture recognizer and prevented by returning nil
    //UI will freeze after calling super.popViewController
    //so that, in order to solve the problem we should not return nil from popViewController
    //we interrupt the call made by gesture recognizer to popViewController through
    //returning false on gestureRecognizerShouldBegin
    //
    //tested on iOS 9.3.2 not others
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        let viewController = self.topViewController
        
        if let canPreventPop = viewController as? CanPreventPopProtocol {
            if !canPreventPop.shouldBePopped() {
                return false
            }
        }
        
        return true
    }

}

Solution 10 - Iphone

You can use this one:

if(self.isMovingToParentViewController)
{
    NSLog(@"Pushed");
}
else
{
    NSLog(@"Popped");
}

Solution 11 - Iphone

I needed to also prevent from popping sometimes so the best answer for me was written by Orkhan Alikhanov. But it did not work because the delegate was not set, so I made the final version:

import UIKit

class CustomActionsNavigationController: UINavigationController, 
                                         UIGestureRecognizerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }
    
    override func popViewController(animated: Bool) -> UIViewController? {
        if let delegate = topViewController as? CustomActionsNavigationControllerDelegate {
            guard delegate.shouldPop() else { return nil }
        }
        return super.popViewController(animated: animated)
    }
    
    // important to prevent UI thread from freezing
    //
    // if popViewController is called by gesture recognizer and prevented by returning nil
    // UI will freeze after calling super.popViewController
    // so that, in order to solve the problem we should not return nil from popViewController
    // we interrupt the call made by gesture recognizer to popViewController through
    // returning false on gestureRecognizerShouldBegin
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if let delegate = topViewController as? CustomActionsNavigationControllerDelegate {
            if !delegate.shouldPop() {
                return false
            }
        }
    
        // This if statement prevents navigation controller to pop when there is only one view controller
        if viewControllers.count == 1 {
            return false
        }

        return true
    }
}

protocol CustomActionsNavigationControllerDelegate {
    func shouldPop() -> Bool
}

UPDATE

I have added viewControllers.count == 1 case, because if there is one controller in the stack and user makes the gesture, it will freeze the UI of your application.

Solution 12 - Iphone

You can observe the notification:

- (void)viewDidLoad{
    [super viewDidLoad];
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(navigationControllerWillShowViewController:) name:@"UINavigationControllerWillShowViewControllerNotification" object:nil];
}

- (void)navigationControllerDidShowViewController:(NSNotification *)notification{
    UIViewController *lastVisible = notification.userInfo[@"UINavigationControllerLastVisibleViewController"];
    if(lastVisible == self){
        // we are being popped
    }
}

Solution 13 - Iphone

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    const BOOL removingFromParent = ![self.navigationController.viewControllers containsObject:self.parentViewController];
    if ( removingFromParent ) {
	    // cleanup
    }
}

Solution 14 - Iphone

Maybe you could use UINavigationBarDelegate's navigationBar:shouldPopItem protocol method.

Solution 15 - Iphone

Try making this check in viewwilldisappear if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound) { //popping of this view has happend. }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJasarienView Question on Stackoverflow
Solution 1 - IphoneJeff MarinoView Answer on Stackoverflow
Solution 2 - IphonecaoimghginView Answer on Stackoverflow
Solution 3 - Iphonema11hew28View Answer on Stackoverflow
Solution 4 - IphoneTom ElliottView Answer on Stackoverflow
Solution 5 - IphoneRonald NepsundView Answer on Stackoverflow
Solution 6 - IphonedieselmcfaddenView Answer on Stackoverflow
Solution 7 - IphonehiepndView Answer on Stackoverflow
Solution 8 - IphoneOded Ben DovView Answer on Stackoverflow
Solution 9 - IphoneOrkhan AlikhanovView Answer on Stackoverflow
Solution 10 - IphoneitechnicianView Answer on Stackoverflow
Solution 11 - IphoneSimon MoshenkoView Answer on Stackoverflow
Solution 12 - IphonemalhalView Answer on Stackoverflow
Solution 13 - IphoneAleksey MazurenkoView Answer on Stackoverflow
Solution 14 - IphoneFrançois P.View Answer on Stackoverflow
Solution 15 - IphoneravoorinandanView Answer on Stackoverflow