3
votes

Based on the Apple documentation I came up with the following method to switch between controllers in a containment controller. When there is an oldC I am getting Unbalanced calls to begin/end appearance transitions for <...> on the console.

- (void) showController:(UIViewController*)newC withView:(UIView*)contentView animated:(BOOL)animated
{
    UIViewController *oldC = self.childViewControllers.firstObject;
    if (oldC == newC) {
        return;
    }

    [oldC willMoveToParentViewController:nil];

    [self addChildViewController:newC];
    newC.view.frame = (CGRect){ 0, 0, contentView.frame.size };
    [contentView addSubview:newC.view];

    if (animated && oldC != nil) {
        oldC.view.alpha = 1.0f;
        newC.view.alpha = 0.0f;
        [self transitionFromViewController:oldC toViewController:newC duration:0.25f options:0 animations:^{

            oldC.view.alpha = 0.0f;
            newC.view.alpha = 1.0f;

         } completion:^(BOOL finished) {
            [oldC removeFromParentViewController];
            [newC didMoveToParentViewController:self];
         }];
    } else {
        oldC.view.alpha = 0.0f;
        newC.view.alpha = 1.0f;
        [oldC removeFromParentViewController];
        [newC didMoveToParentViewController:self];
    }
}

This is how I call it:

- (IBAction) buttonSignIn:(id)sender
{
    [self showController:self.signInViewController withView:self.contentView animated:(sender != nil)];
}

- (IBAction) buttonSignUp:(id)sender
{
    [self showController:self.signUpViewController withView:self.contentView animated:(sender != nil)];
}

To track this down I am logging the appearance transitions

-(void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated
{
    [super beginAppearanceTransition:isAppearing animated:animated];
    NSLog(@"**begin %@", self);
}

-(void)endAppearanceTransition
{
    [super endAppearanceTransition];
    NSLog(@"**end** %@", self);
}

This is what the log looks like:

] **begin <SignInViewController: 0x10c769a20>
] **begin <SignUpViewController: 0x10c768770>
] Unbalanced calls to begin/end appearance transitions for <SignUpViewController: 0x10c768770>.
] **end** <SignUpViewController: 0x10c768770>
] **end** <SignInViewController: 0x10c769a20>

Now I am a little puzzled. What's the problem here?

3

3 Answers

16
votes

Turns out transitionFromViewController:toViewController:duration:options:animations:completion: also adds the view.

This method adds the second view controller’s view to the view hierarchy and then performs the animations defined in your animations block. After the animation completes, it removes the first view controller’s view from the view hierarchy.

Which means the addSubview needs to be adjusted accordingly.

- (void) showController:(UIViewController*)newC withView:(UIView*)contentView animated:(BOOL)animated
{
    UIViewController *oldC = self.childViewControllers.firstObject;
    if (oldC == newC) {
        return;
    }

    [oldC willMoveToParentViewController:nil];

    [self addChildViewController:newC];
    newC.view.frame = (CGRect){ 0, 0, contentView.frame.size };

    if (animated && oldC != nil) {
        oldC.view.alpha = 1.0f;
        newC.view.alpha = 0.0f;
        [self transitionFromViewController:oldC toViewController:newC duration:0.25f options:0 animations:^{

            oldC.view.alpha = 0.0f;
            newC.view.alpha = 1.0f;

         } completion:^(BOOL finished) {
            [oldC removeFromParentViewController];
            [newC didMoveToParentViewController:self];
         }];
    } else {
        [contentView addSubview:newC.view];
        oldC.view.alpha = 0.0f;
        newC.view.alpha = 1.0f;
        [oldC removeFromParentViewController];
        [newC didMoveToParentViewController:self];
    }
}
1
votes

old code

[self addChildViewController:toVC];
[fromVC willMoveToParentViewController:nil];
[self.view addSubview:toVC.view];
[toVC.view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.mas_equalTo(0);
 }];

 [self transitionFromViewController:fromVC
                      toViewController:toVC duration:0
                               options:UIViewAnimationOptionTransitionNone
                            animations:^{
                            } completion:^(BOOL finished) {
                                [fromVC.view removeFromSuperview];
                                [fromVC removeFromParentViewController];
                                [toVC didMoveToParentViewController:self];
                                self.currentVC = toVC;
                            }];

new code, it's ok

[self addChildViewController:toVC];
[fromVC willMoveToParentViewController:nil];

    [self transitionFromViewController:fromVC
                      toViewController:toVC duration:0
                               options:UIViewAnimationOptionTransitionNone
                            animations:^{
                            } completion:^(BOOL finished) {
                                [fromVC.view removeFromSuperview];
                                [fromVC removeFromParentViewController];
                                [toVC didMoveToParentViewController:self];
                                self.currentVC = toVC;
                            }];
0
votes

I set the duration to 0, which means animation:NO for the transitionFromViewControllercall.