viewWillDisappear: Determine whether view controller is being popped or is showing a sub-view controller

IosIphoneCocoa TouchUiviewcontrollerUikit

Ios Problem Overview


I'm struggling to find a good solution to this problem. In a view controller's -viewWillDisappear: method, I need to find a way to determine whether it is because a view controller is being pushed onto the navigation controller's stack, or whether it is because the view controller is disappearing because it has been popped.

At the moment I'm setting flags such as isShowingChildViewController but it's getting fairly complicated. The only way I think I can detect it is in the -dealloc method.

Ios Solutions


Solution 1 - Ios

You can use the following.

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];
  NSArray *viewControllers = self.navigationController.viewControllers;
  if (viewControllers.count > 1 && [viewControllers objectAtIndex:viewControllers.count-2] == self) {
    // View is disappearing because a new view controller was pushed onto the stack
    NSLog(@"New view controller was pushed");
  } else if ([viewControllers indexOfObject:self] == NSNotFound) {
    // View is disappearing because it was popped from the stack
    NSLog(@"View controller was popped");
  }
}

This is, of course, possible because the UINavigationController's view controller stack (exposed through the viewControllers property) has been updated by the time that viewWillDisappear is called.

Solution 2 - Ios

I think the easiest way is:

 - (void)viewWillDisappear:(BOOL)animated
{
    if ([self isMovingFromParentViewController])
    {
        NSLog(@"View controller was popped");
    }
    else
    {
        NSLog(@"New view controller was pushed");
    }
    [super viewWillDisappear:animated];
}

Swift:

override func viewWillDisappear(animated: Bool)
{
	if isMovingFromParent
	{
		print("View controller was popped")
	}
	else
	{
		print("New view controller was pushed")
	}
    super.viewWillDisappear(animated)
}

Solution 3 - Ios

From Apple's Documentation in UIViewController.h :

> "These four methods can be used in a view controller's appearance > callbacks to determine if it is being presented, dismissed, or added > or removed as a child view controller. For example, a view controller > can check if it is disappearing because it was dismissed or popped > by asking itself in its viewWillDisappear: method by checking the > expression ([self isBeingDismissed] || [self > isMovingFromParentViewController])." > > - (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0); > > - (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0); > > - (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0); > > - (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);

So yes, the only documented way to do this is the following way :

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if ([self isBeingDismissed] || [self isMovingFromParentViewController]) {
    }
}

Swift 3 version:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if self.isBeingDismissed || self.isMovingFromParentViewController { 
    }
}

Solution 4 - Ios

If you just want to know whether your view is getting popped, I just discovered that self.navigationController is nil in viewDidDisappear, when it is removed from the stack of controllers. So that's a simple alternative test.

(This I discover after trying all sorts of other contortions. I'm surprised there's no navigation controller protocol to register a view controller to be notified on pops. You can't use UINavigationControllerDelegate because that actually does real display work.)

Solution 5 - Ios

Swift 4

override func viewWillDisappear(_ animated: Bool)
    {
        if self.isMovingFromParent
        {
            //View Controller Popped
        }
        else
        {
            //New view controller pushed
        }
       super.viewWillDisappear(animated)
    }

Solution 6 - Ios

In Swift:

 override func viewWillDisappear(animated: Bool) {
    if let navigationController = self.navigationController {
        if !contains(navigationController.viewControllers as! Array<UIViewController>, self) {
        }
    }
    
    super.viewWillDisappear(animated)

}

Solution 7 - Ios

I find Apple's documentation on this is hard to understand. This extension helps see the states at each navigation.

extension UIViewController {
    public func printTransitionStates() {
        print("isBeingPresented=\(isBeingPresented)")
        print("isBeingDismissed=\(isBeingDismissed)")
        print("isMovingToParentViewController=\(isMovingToParentViewController)")
        print("isMovingFromParentViewController=\(isMovingFromParentViewController)")
    }
}

Solution 8 - Ios

Thanks @Bryan Henry, Still works in Swift 5

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if let controllers = navigationController?.children{
            if controllers.count > 1, controllers[controllers.count - 2] == self{
                // View is disappearing because a new view controller was pushed onto the stack
                print("New view controller was pushed")
            }
            else if controllers.firstIndex(of: self) == nil{
                // View is disappearing because it was popped from the stack
                print("View controller was popped")
            }
        }
        
    }

Solution 9 - Ios

This question is fairly old but I saw it by accident so I want to post best practice (afaik)

you can just do

if([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
 // view controller popped
}

Solution 10 - Ios

This applies to iOS7, no idea if it applies to any other ones. From what I know, in viewDidDisappear the view already has been popped. Which means when you query self.navigationController.viewControllers you will get a nil. So just check if that is nil.

TL;DR

 - (void)viewDidDisappear:(BOOL)animated
 {
    [super viewDidDisappear:animated];
    if (self.navigationController.viewControllers == nil) {
        // It has been popped!
        NSLog(@"Popped and Gone");
    }
 }

Solution 11 - Ios

Segues can be a very effective way of handling this problem in iOS 6+. If you have given the particular segue an identifier in Interface Builder you can check for it in prepareForSegue.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"LoginSegue"]) {
       NSLog(@"Push");
       // Do something specific here, or set a BOOL indicating
       // a push has occurred that will be checked later
    }
}

Solution 12 - Ios

I assume you mean that your view is being moved down the navigation controller's stack by the pushing a new view when you say pushed onto the stack. I would suggest using the viewDidUnload method to add a NSLog statement to write something to the console so you can see what is going on, you may want to add a NSLog to viewWillDissappeer.

Solution 13 - Ios

Here is a category to accomplish the same thing as sbrocket's answer:

Header:

#import <UIKit/UIKit.h>

@interface UIViewController (isBeingPopped)

- (BOOL) isBeingPopped;

@end

Source:

#import "UIViewController+isBeingPopped.h"

@implementation UIViewController (isBeingPopped)

- (BOOL) isBeingPopped {
    NSArray *viewControllers = self.navigationController.viewControllers;
    if (viewControllers.count > 1 && [viewControllers objectAtIndex:viewControllers.count-2] == self) {
        return NO;
    } else if ([viewControllers indexOfObject:self] == NSNotFound) {
        return YES;
    }
    return NO;
}

@end

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
QuestionMichael WaterfallView Question on Stackoverflow
Solution 1 - IosBryan HenryView Answer on Stackoverflow
Solution 2 - IosRTascheView Answer on Stackoverflow
Solution 3 - IosShyam BhatView Answer on Stackoverflow
Solution 4 - Iosdk.View Answer on Stackoverflow
Solution 5 - IosUmairView Answer on Stackoverflow
Solution 6 - Iosuser754905View Answer on Stackoverflow
Solution 7 - IosNormanView Answer on Stackoverflow
Solution 8 - IosdengST30View Answer on Stackoverflow
Solution 9 - Iosuser1396236View Answer on Stackoverflow
Solution 10 - IosByteView Answer on Stackoverflow
Solution 11 - IosKyle CleggView Answer on Stackoverflow
Solution 12 - IosAaronView Answer on Stackoverflow
Solution 13 - IosbbrameView Answer on Stackoverflow