Navigation bar gets adjusted after calling completeTransition: in custom transition
Asked Answered
D

4

18

My goal is to provide zooming modal transition from for the user from a view similar as springboard icons zoom in when launching apps.

The presented view controller zooms in correctly, but the navigation bar has wrong position under the status bar. This position gets corrected after calling [transitionContext completeTransition:finished];. How can I make it correct from the beginning of the transition?

This is a screen recording of the bug: http://youtu.be/7LKU4lzb-uw (the glitch is in the 6th second of the recording)

The UIViewControllerAnimatedTransitioning code:

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *container = [transitionContext containerView];

    CGPoint viewCenter = self.view.center;
    CGSize viewSize = self.view.frame.size;
    CGSize controllerSize = toViewController.view.frame.size;

    CGFloat controllerFromX = viewCenter.x - (controllerSize.width / 2);
    CGFloat controllerFromY = viewCenter.y - (controllerSize.height / 2);

    CGAffineTransform transform = CGAffineTransformMakeTranslation(controllerFromX, controllerFromY);
    transform = CGAffineTransformScale(transform, viewSize.width / controllerSize.width, viewSize.height / controllerSize.height);

    if (self.reverse) {
        [container insertSubview:toViewController.view belowSubview:fromViewController.view];
    } else {
        toViewController.view.transform = transform;
        [container addSubview:toViewController.view];
    }

    [UIView animateKeyframesWithDuration:ZoomTransitioningDuration 
                                   delay:0 
                                 options:0 
                              animations:^{
                if (self.reverse) {
                    fromViewController.view.alpha = 0.0f;
                    fromViewController.view.transform = transform;
                } else {
                    toViewController.view.transform = CGAffineTransformIdentity;
                }
        } 
                              completion:^(BOOL finished) {
                [transitionContext completeTransition:finished];
        }];
}
Darfur answered 21/9, 2013 at 19:12 Comment(4)
Can't you adjust the y origin of corresponding view?Gooseflesh
When I do that, the area below status bar is black, not green (the whole controller is shifted downwards).Chrissy
Where does self.view come from? Is the presenting view controller also the animation controller?Challis
Can you make the video public again? There hasn't been a good, non-hacky solution yet, so it would be helpful to see the video.Tamatamable
H
28

The problem is that you are setting the transform before inserting the destination view controller's view into the container.

Switching the order should fix it:

if (self.reverse) {
    [container insertSubview:toViewController.view belowSubview:fromViewController.view];
} else {
    [container addSubview:toViewController.view];
    toViewController.view.transform = transform;
}

See point 4 here. Since you've applied a transform prior to inserting the navigation controller's view as a subview, the layout engine doesn't think the navigation bar is at the top edge of the window, and therefore doesn't need to be adjusted to avoid the status bar.

Helios answered 23/10, 2013 at 0:56 Comment(1)
Point 4 of that post is a godsend.Tamatamable
D
6

I've found a solution, although pretty hacky. I have to manually adjust the navigation bar frame before the animation starts:

if (self.reverse) {
    [container insertSubview:toViewController.view belowSubview:fromViewController.view];
} else {
    toViewController.view.transform = transform;
    [container addSubview:toViewController.view];

    // fix navigation bar position to prevent jump when completeTransition: is called
    if ([toViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*) toViewController;
        UINavigationBar* bar = navigationController.navigationBar;
        CGRect frame = bar.frame;
        bar.frame = CGRectMake(frame.origin.x, frame.origin.y + 20.0f, frame.size.width, frame.size.height);
    }
}
Darfur answered 25/9, 2013 at 19:14 Comment(4)
The problem with this is that it only fixes the problem the first time. Presenting the same view controller again later will cause the nav bar to be 20 pixels too low.Helios
it's works fine, but not for colored navigation bar.Kizer
bar.frame = CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height + 20.0f); is much better ;)Kizer
But still it makes subviews glitching.Kizer
B
1

Add the toViewControllerView first to the ContainerView, then set the toViewControllerView transform as given below.

[container addSubview:toViewController.view];

toViewController.view.transform = transform;

This will solve the problem.

Bolen answered 4/11, 2014 at 9:27 Comment(0)
L
0

This is still a hack, based on Ondřej Mirtes' one but it works better if you have an in-call status bar and you're on iOS8

if([toViewController isKindOfClass:[UINavigationController class]]) { 
  UINavigationController *navCtrl = (UINavigationController *)toViewController;
  UINavigationBar *navBar = navCtrl.navigationBar;
  if(navBar.frame.origin.y == 0 && navBar.frame.size.height == 44) {
    navBar.frame = CGRectMake(0, 0, navBar.frame.size.width, fmin(44 + [UIApplication sharedApplication].statusBarFrame.size.height, 64)); 
  }
}

Remains ugly though :/

Liven answered 29/9, 2014 at 11:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.