Linda Keating
Linda Keating

Reputation: 2425

Unbalanced calls to begin/end appearance transitions for <UIViewController: 0x176c0bd0> within a Navigation Controller

enter image description hereI am trying to write a custom segue and have come across this error

Unbalanced calls to begin/end appearance transitions for UIViewController: 0x176c0bd0

The help button is connected to the almost empty ViewController - and the exit button unwinds the segue

All the controllers are embedded in a navigation Controller.

I've read through various posts here where people have encountered the same problem, but the solution varies a lot, and I still haven't found the right solution. I think it is because I am calling the custom segue from within a Navigation Controller, but that my code doesn't reflect that. I've followed this tutorial to create the custom segue http://blog.dadabeatnik.com/2013/10/13/custom-segues/

The initial controller has the following methods:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([segue isKindOfClass:[ICIHelpSegue class]]) {
        ((ICIHelpSegue *)segue).originatingPoint = self.help.center;
    }      
}

- (IBAction)unwindFromViewController:(UIStoryboardSegue *)sender {
}

- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
     ICIUnwindHelpSegue *segue = [[ICIUnwindHelpSegue alloc] initWithIdentifier:identifier source:fromViewController destination:toViewController];
    segue.targetPoint = self.help.center;
    return segue;
}

The ICIHelpSegue class is the following interface:

    @interface ICIHelpSegue : UIStoryboardSegue

     @property CGPoint originatingPoint;
     @property CGPoint targetPoint;

    @end

And the implementation file looks like this:

@implementation ICIHelpSegue
- (void)perform {
    UIViewController *sourceViewController = self.sourceViewController;
    UIViewController *destinationViewController = self.destinationViewController;
    UINavigationController *navigationController = sourceViewController.navigationController;

    [navigationController.view addSubview:destinatiionViewController.view]

    // Transformation start scale
    destinationViewController.view.transform = CGAffineTransformMakeScale(0.05, 0.05);

    // Store original centre point of the destination view
    CGPoint originalCenter = destinationViewController.view.center;
    // Set center to start point of the button
    destinationViewController.view.center = self.originatingPoint;

    [UIView animateWithDuration:0.5
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseInOut
                     animations:^{
                         // Grow!
                         destinationViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);
                         destinationViewController.view.center = originalCenter;
                     }
                     completion:^(BOOL finished){
                         [destinationViewController.view removeFromSuperview]; // remove from temp super view
                         [navigationController presentViewController:destinationViewController animated:NO completion:NULL]; // present VC
                     }];
}

@end

Any ideas why this error occurs? What it means? And how to solve it?

Upvotes: 2

Views: 2623

Answers (1)

cncool
cncool

Reputation: 1026

I have the same error. The issue appears to be related to the fact that removeFromSuperview is called in the same run loop as the call to presentViewController:animated:completion for the same viewController/view.

One thing you can do to get rid of this warning is to present the view controller after a delay:

    completion:^(BOOL finished)
    {
        destinationViewController.view removeFromSuperview];
        [navigationController performSelector:@selector(presentViewController:) withObject:destinationViewController afterDelay:0];
    }];

}

-(void)presentViewController:(UIViewController*)viewController
{
    [[self presentViewController:viewController animated:NO completion:nil];  
}

However, both this method and the one with the warning will sometimes have a flicker because there is one frame where the view has been removed from the superview, but not presented yet.

To get around this issue, you can create a snapshot view of the destination view controller, and add that to the view hierarchy in place of the actual destinationViewController's view, and then remove it AFTER the destinationViewController has been presented:

    completion:^(BOOL finished)
    {
        UIView * screenshotView = [destinationViewController.view snapshotViewAfterScreenUpdates:NO];

        screenshotView.frame = destinationViewController.view.frame; 

        [destinationViewController.view.superview insertSubview:screenshotView aboveSubview: destinationViewController.view];
        [destinationViewController.view removeFromSuperview];

        [self performSelector:@selector(presentViewControllerRemoveView:) withObject:@[destinationViewController, screenshotView] afterDelay:0];


     }];
}

-(void)presentViewControllerRemoveView:(NSArray *)objects
{
    UIViewController * viewControllerToPresent = objects[0];
    UIView * viewToRemove = objects[1];

    [self presentViewController:viewControllerToPresent
                       animated:NO
                     completion:
    ^{
        [viewToRemove removeFromSuperview];
    }];
}

Upvotes: 7

Related Questions