iOS: popViewController unexpected behavior
Asked Answered
H

7

49

I've been searching the internet for a solution. There's nothing I could find. So: I'm using a UINavigationController. I am pushing two UIViewControllers onto it. In the second pushed ViewController i am executing this code:

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error {
NSLog([error localizedDescription]);
[self.navigationController popViewControllerAnimated:YES]; }

The expected thing to happen would be that the last pushed ViewController disappears. In this app I am doing this on few places and it works fine everywhere expect in this very ViewController. What happens is that only the back button goes off screen (animated) but everything else stays on screen. In the Console Output two things are printed out when this line executes:

2011-03-14 16:32:44.580 TheAppXY[18518:207] nested pop animation can result in corrupted navigation bar

2011-03-14 16:32:53.507 TheAppXY[18518:207] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.

Two error messages I couldn't find ANY information on. I'm using XCode 4 and iOS SDK 4.3. Maybe anyone can help me with this problem.

Hundredweight answered 14/3, 2011 at 15:51 Comment(2)
I think I figured it out by myself. I think it's because I try to pop the view controller too early. If the Reverse Geocoder delivered an adress (which is started in ViewDidLoad) and failed, the view didn't appear yet, so animated popping isn't working well obviously. I now implemented the start of the Reverse Geocoder in ViewDidAppear and everything seems to be working fine. Novice mistake more or less. But i wonder that there is no information to find about itHundredweight
Recently, I've faced the same problem. The reason was: -I was trying to pop view controller twice by mistake. you can check this crash by setting breakpoints on push and pop View controllersTheoretician
A
50

I came across a similar situation in my code and the message said:

nested push animation can result in corrupted navigation bar

Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree >might get corrupted.

My finding to this issue was that I was pushing 2 view controllers one after the other in quick succession and both were animated.

In your case it seems that you might be popping multiple view controllers with animation one after the other.

Hence, while one view is undergoing animation you should not start animation on another view.

I also found that if I disabled animation on one view, the error message disappeared.

In my case it was a problem with the flow logic as I did not intend to push 2 view controllers one after the other. One was being pushed within the switch case logic and another after its end.

Hope this helps someone.

Agar answered 19/5, 2011 at 1:49 Comment(4)
They key here for me was: "... and both were animated." Thanks!Insured
Do you have any recommendations for handling situations where you would want to do this? For example, I see this a lot when I push a view controller onto the stack, and then I show a UIAlertView. I still want to show both of them, I would just prefer that the View get pushed on the stack, and then the alert view show up without having to implement a bunch of delegate methods.Septavalent
Thanks when I changed to this Animated:YES to Animated:NO and call my pop method on viewDidAppear solved the problemVulgus
not an elegant solution but i got rid of this error by putting a 0.5 second delay on second action, because setting animated no to first popviewcontroller didnt work for me.Billi
N
27

You can get this anytime that you try to pop before viewDidAppear. If you set a flag, then just check that flag in viewDidAppear, you wont have a problem.

Naturalistic answered 6/5, 2011 at 21:1 Comment(1)
This seems to be the safest approachPluralism
C
12

I have created a drop-in replacement for UINavigationController that will queue animations for you and avoid this problem entirely.

Grab it from BufferedNavigationController

Caudal answered 20/12, 2011 at 8:19 Comment(2)
Hey andrew, do you still support the above class? For some reason it's not working for me as you can see here: #16634837Macpherson
See also my UINavigationControllerWithQueue answer below, for an alternativeTychonn
G
3

I had this problem, too, and here's what was causing mine:

  1. In RootViewController, I am using several UISegmentedControl objects to determine which of many views to load next.
  2. In that (sub/2nd) view, I was popping (by using the "Back" button) back to RootViewController.
  3. In RootViewController, I was handling viewWillAppear to "reset" each of my UISegmentedControl objects to a selectedSegmentIndex of -1 (meaning no segment looks "pressed").
  4. That "reset" triggered each of my UISegmentedControl objects to fire their associated (and separate) IBActions.
  5. Since I wasn't handling for a "selection" of -1, I had multiple methods firing at the same time, all trying to push a different view.

My fix? I tightened up my if...then statements and bailed on executing any code in my UISegmentedControl IBActions when selectedSegmentIndex == -1.

I'm still not sure why I got "pop" animation errors and not "push" errors, but at least figured out my error and got it fixed!

Hope this helps someone else!

Garceau answered 14/6, 2011 at 2:3 Comment(0)
C
0

yeah, unfortunately apple did not synchronize UINavigationController's animations. Andrew's solution is excellent, but if you don't want to cover its whole functionality, there is a simpler solution, override these two methods :

// navigation end event

- ( void )  navigationController    : ( UINavigationController* ) pNavigationController 
            didShowViewController   : ( UIViewController*       ) pController 
            animated                : ( BOOL                    ) pAnimated
{

    if ( [ waitingList count ] > 0 ) [ waitingList removeObjectAtIndex : 0 ];
    if ( [ waitingList count ] > 0 ) [ super pushViewController : [ waitingList objectAtIndex : 0 ] animated : YES ];

}


- ( void )  pushViewController  : ( UIViewController* ) pController 
            animated            : ( BOOL ) pAnimated
{

    [ waitingList addObject : pController ];
    if ( [ waitingList count ] == 1 ) [ super pushViewController : [ waitingList objectAtIndex : 0 ] animated : YES ];

}

and create an NSMutableArray instance variable called waitingList, and you are done.

Celandine answered 1/3, 2012 at 23:57 Comment(1)
I took this idea and generalized it to also work with popViewController* in the answer below, UINavigationControllerWithQueueTychonn
V
0

This problem happen with me when i use storyboards. I've made a mistake: I have a UIButton with an action to performSegueWithIdentifier. So i link the push segue with Button with the other ViewController so occur this problem.

To solve: Link the button action in UIButton and link the push segue between two ViewControllers.

Vibraharp answered 14/8, 2013 at 16:0 Comment(0)
T
0

Combining MilGra and Andrew's answers gave me something that works reliably and is a simpler drop-in UINavigationController replacement.

This improves on MilGra's answer to make it work with pushes and pops, but is simpler than Andrew's BufferedNavigationController. (Using BufferedNavigationController I was occasionally getting transitions that would never complete and would only show a black screen.)

This whole thing seems not to be necessary on iOS8, but was still needed for me on iOS7.

@implementation UINavigationControllerWithQueue {
    NSMutableArray *waitingList;
}

-(void) viewDidLoad {
    [super viewDidLoad];
    self.delegate = self; // NOTE: delegate must be self!
    waitingList = [[NSMutableArray alloc] init];
}

# pragma mark - Overrides

-(void) pushViewController: (UIViewController*) controller
                  animated: (BOOL) animated {
    [self queueTransition:^{ [super pushViewController:controller animated:animated]; }];
}

- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    UIViewController *result = [self.viewControllers lastObject];
    [self queueTransition:^{ [super popViewControllerAnimated:animated]; }];
    return result;
}

- (NSArray*)popToRootViewControllerAnimated:(BOOL)animated {
    NSArray* results = [self.viewControllers copy];
    [self queueTransition:^{ [super popToRootViewControllerAnimated:animated]; }];
    return results;
}

# pragma mark - UINavigationControllerDelegate

-(void) navigationController: (UINavigationController*) navigationController
       didShowViewController: (UIViewController*) controller
                    animated: (BOOL) animated {
    [self dequeTransition];
}

# pragma mark - Private Methods

-(void) queueTransition:(void (^)()) transition {
    [waitingList addObject:transition];
    if (waitingList.count == 1) {
        transition();
    }
}

-(void) dequeTransition {
    if (waitingList.count > 0) {
        [waitingList removeObjectAtIndex:0];
    }
    if (waitingList.count > 0) {
        void (^transition)(void) = [waitingList objectAtIndex:0];
        if (transition) {
            transition();
        }
    }
}

@end
Tychonn answered 20/6, 2015 at 7:32 Comment(1)
Worked great in the simulator, but not reliably on the device -- so in the end, not a recommended approach.Tychonn

© 2022 - 2024 — McMap. All rights reserved.