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

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.


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.


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.