iOS how to detect programmatically when top view controller is popped?

IosUinavigationcontrollerStack

Ios Problem Overview


Suppose I have a nav controller stack with 2 view controllers: VC2 is on top and VC1 is underneath. Is there code I can include in VC1 that will detect that VC2 has just been popped off the stack?

Since I'm trying to detect the popping of VC2 from within the code for VC1 it seems that something like viewWillAppear or viewDidAppear won't work, because those methods fire every time VC1 is displayed, including when it is first pushed on the stack.

EDIT: it seems I was not very clear with my original question. Here's what I'm trying to do: determine when VC1 is being shown due to VC2 being popped off the top of the stack. Here's what I'm NOT trying to do: determine when VC1 is being shown due to being pushed onto the top of the stack. I need some way that will detect the first action but NOT the second action.

Note: I don't particularly care about VC2, it can be any number of other VCs that get popped off the stack, what I do care about is when VC1 becomes the top of the stack again due to some other VC begin popped off the top.

Ios Solutions


Solution 1 - Ios

iOS 5 introduced two new methods to handle exactly this type of situation. What you're looking for is -[UIViewController isMovingToParentViewController]. From the docs:

> isMovingToParentViewController > > Returns a Boolean value that indicates > that the view controller is in the process of being added to a parent. > > - (BOOL)isMovingToParentViewController > > Return Value
> YES if the view controller is appearing because it was added as a child of a container > view controller, otherwise NO. > > Discussion
> This method returns YES only when called from inside the > following methods: > > -viewWillAppear:
> -viewDidAppear:

In your case you could implement -viewWillAppear: like so:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    if (self.isMovingToParentViewController == NO)
    {
        // we're already on the navigation stack
        // another controller must have been popped off
    }
}

EDIT: There's a subtle semantic difference to consider here—are you interested in the fact that VC2 in particular popped off the stack, or do you want to be notified each time VC1 is revealed as a result of any controller popping? In the former case, delegation is a better solution. A straight-up weak reference to VC1 could also work if you never intend on reusing VC2.

EDIT 2: I made the example more explicit by inverting the logic and not returning early.

Solution 2 - Ios

isMovingTo/FromParentViewController won't work for pushing and popping into a navigation controller stack.

Here's a reliable way to do it (without using the delegate), but it's probably iOS 7+ only.

UIViewController *fromViewController = [[[self navigationController] transitionCoordinator] viewControllerForKey:UITransitionContextFromViewControllerKey];

if ([[self.navigationController viewControllers] containsObject:fromViewController])
{
    //we're being pushed onto the nav controller stack.  Make sure to fetch data.
} else {
    //Something is being popped and we are being revealed
}

In my case, using the delegate would mean having the view controllers' behavior be more tightly coupled with the delegate that owns the nav stack, and I wanted a more standalone solution. This works.

Solution 3 - Ios

One way you could approach this would be to declare a delegate protocol for VC2 something like this:

in VC1.h

@interface VC1 : UIViewController <VC2Delegate> {
...
}

in VC1.m

-(void)showVC2 {
    VC2 *vc2 = [[VC2 alloc] init];
    vc2.delegate = self;
    [self.navigationController pushViewController:vc2 animated:YES];
}

-(void)VC2DidPop {
    // Do whatever in response to VC2 being popped off the nav controller
}

in VC2.h

@protocol VC2Delegate <NSObject>
-(void)VC2DidPop;
@end

@interface VC2 : UIViewController {

    id<VC2Delegate> delegate;
}

@property (nonatomic, assign) id delegate;

...

@end

VC2.m

-(void)viewDidUnload {
    [super viewDidUnload];
    [self.delegate VC2DidPop];
}

There's a good article on the basics of protocols and delegates here.

Solution 4 - Ios

You can also detect in the view controller that is being popped

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

Solution 5 - Ios

This is worked for me

UIViewController *fromViewController = [[[self navigationController] transitionCoordinator] viewControllerForKey:UITransitionContextFromViewControllerKey];
if (![[self.navigationController viewControllers] containsObject:fromViewController] && !self.presentedViewController)
{
  //Something is being popped and we are being revealed 
}

Solution 6 - Ios

What are you specifically trying to do?

If you're trying to detect that VC1 is about to be shown, this answer should help you. Use UINavigationControllerDelegate.

If you're trying to detect that VC2 is about to be hidden, I would just use the viewWillDisappear: of VC2.

Solution 7 - Ios

You can add an observer for NSNotification specifically JUST for your VC2.

// pasing the "VC2" here will tell the notification to only listen for notification from
// VC2 rather than every single other objects
[[NSNotitificationCenter defaultCenter] addObserver:self selector:@selector(doSomething:) object:VC2];

Now in in your VC2's view will disappear, you can post a notification:

-(void)viewWillDisappear
{
    [[NSNotificationCenter defaultCenter] postNotificationNamed:@"notif_dismissingVC2" object:nil];
}

Solution 8 - Ios

swift 3

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if self.isMovingToParentViewController {
            print("View is moving to ParentViewControll")
        }
}

Solution 9 - Ios

I got the same situation but with slight more specific use case. In my case we wanted to determine if a VC1 is appeared/displayed when user tap on VC2's back button where VC2 is pushed on navigationController over VC1.

So I used help of snarshad's answer to customised as per my need. Here is the code in VC1's viewDidAppear in swift 3.

// VC1: ParentViewController
// VC2: ChildViewController

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        if let transitionCoordinator = navigationController?.transitionCoordinator,
            let fromVC = transitionCoordinator.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toVC = transitionCoordinator.viewController(forKey: UITransitionContextViewControllerKey.to),
            fromVC is ChildViewController,
            toVC is ParentViewController {
            
            print("Back button pressed on ChildViewController, and as a result ParentViewController appeared")
        }
    }

Solution 10 - Ios

Yes, in VC1 you can check whether VC2 is popped or not. UINavigationController there is one method viewControllers which will returns the array of pushed Controllers, which are in the stack (i.e. which have been pushed).

So you iterate through loop by comparing class. If VC2 is there, will have match, otherwise not.

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
QuestionJMLdevView Question on Stackoverflow
Solution 1 - IosRyder MackayView Answer on Stackoverflow
Solution 2 - IossnarshadView Answer on Stackoverflow
Solution 3 - IosBrad EatonView Answer on Stackoverflow
Solution 4 - IosSaren IndenView Answer on Stackoverflow
Solution 5 - IoschuanfengView Answer on Stackoverflow
Solution 6 - Iosuser244343View Answer on Stackoverflow
Solution 7 - IosZhangView Answer on Stackoverflow
Solution 8 - IosiPareshView Answer on Stackoverflow
Solution 9 - IosBLCView Answer on Stackoverflow
Solution 10 - IosSanoj KashyapView Answer on Stackoverflow