25
votes

I implemented a custom UIViewController Transition in my App, which replaces the navigation controllers built in push animation.

Everything works so far, except the toplayoutguide in the newly pushed view controller is 0 although the new view controller inherited the navigation bar from the old view controller. It should be 64.0 (Statusbar height + Navigation bar height), where it is 0.0 now. So all objects, which are attached to the top layout guide in the storyboard now appear 64 points too high (below the translucent bar).

When I disable the custom View Transition the top layout guide will have the expected value. I tried to call layoutSubviews and updateConstraints "all over the place". In the view controller as well as in the navigationcontroller.

As I understand the navigationcontroller (parentviewcontroller) should update the toplayoutguide of the new view controller, but apparently I am missing something in my custom transitioning code, which triggers the update to the correct value for the toplayoutguide.

Here's my custom transition code which is an object set as delegate of the navigationcontroller:

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return 0.7;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIView *animationContainerView = [transitionContext containerView];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *toView = [toVC view];
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIView *fromView = [fromVC view];

    CGRect endFrame = [transitionContext finalFrameForViewController:toVC];
    CGRect startFrame;

    startFrame = CGRectOffset(endFrame, 0, endFrame.size.height);

    if (self.operation == UINavigationControllerOperationPop) {
        [animationContainerView insertSubview:toView belowSubview:fromView];
        [toView setFrame:endFrame];
    }
    else{
        [toView setFrame:startFrame];
        [animationContainerView insertSubview:toView aboveSubview:fromView];
    }

    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.7 initialSpringVelocity:0.8 options:UIViewAnimationOptionBeginFromCurrentState animations:^{

        if (self.operation == UINavigationControllerOperationPop) {
            [fromView setFrame:startFrame];
            [fromView layoutIfNeeded];
        }
        else{
            [toView setFrame:endFrame];
            [toView layoutIfNeeded];
        }

    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];
    }];
}

Nothing really fancy happens there. Just the view sliding from the bottom up with some built in dynamics.

The problem is, that the objects attached to the top layout guide now are under the navigation bar, as the top layout guide length == 0.

I can't figure out what I need to do, so that the view controller's toplayoutguide is set to the correct value.

The push navigation is performed "plain vanilla" with a push storyboard segue. All I do, before calling performSegueWithIdentifier is to set the navigationcontrollers delegate.

Here's the code:

    self.navigationController.delegate = [[My_CustomNavigationTransitionDelegate alloc] init];
    [self performSegueWithIdentifier:@"infosSegue" sender:nil];

What do I miss?

4
I'm having the same problem. Maddeningly, after the transition is finished, rotating the device updates the topLayoutGuide to be the correct value. I haven't yet figured out what to call to force the topLayoutGuide to update as part of this transition.Dan Jackson
It looks like a bug to me. I already posted the same question in apples developer forums, but haven't received any answers there. For now I resorted to NOT have a transparent navigation bar, instead of ugly workarounds. Fortunately clients still do not see and understand the advantage of a translucent navigation bar, as they are still used to how it was before. But that will change soon. I hope until then some kind person at Apple will tell us how to force an update of the layout guides...alex da franca
I had the same problem, ended up implementing my own topLayout guide view and making constraints to it rather then to topLayoutGuide. Not ideal. Only posting it here in case someone is stuck and looking for quick hacky solution github.com/stringcode86/SCTopLayoutGuidestringCode
I too did end up with my own top and bottom layout constraints. For now it seems to be the only solution, if you work with storyboards.alex da franca

4 Answers

5
votes

I was having an issue where the bottomLayoutGuide property would set itself to zero length and then would cause my buttons above the tab bar to fall below to tab bar with the autolayout.

Have you looked at doing this

[self.navigationController.view setNeedsLayout]

I put it into my viewwillappear and I stopped getting a zero length on the bottomLayoutGuide property. Maybe that would help you out with your topLayoutGuide property too.

0
votes

I was able to work around this, with the following view hierarchy:

UIView
 UIScrollView
  <content, constrained to UIScrollView>

Constrain the UIScrollView to match the UIView's top, leading, trailing, and bottom edges. Interface Builder might want you to use the topLayoutGuide and bottomLayoutGuide for the UIScrollView, or it might not. Maybe it's dependent on the version of Xcode, but some of our View Controllers used the superview, others used the layout guides.

For the views where Interface Builder didn't want to constraint the scroll view relative to its superview, I opened the storyboard in a text editor and adjusted the constraints on the scroll view by hand.

Finally, on the View Controller, make sure that extend edges under top bar is YES, and so is Adjust Scroll View Insets.

Basically, I'm avoiding using the topLayoutGuide, and instead relying on the scroll view insets, which does work.

Where I didn't have a UIScrollView in the hierarchy, like you, NOT extending edges under the top bar worked for me.

0
votes

I ran into the exact same problem. My custom navigation controller's container view didn't have constraints. The minute I added vertical spacing constraints from the container view to its superview's layout guides (albeit the two were identical in size), and set the top/bottom/status bar appearance on the container view everything was ok and the layout guides of the pushed controllers were in the correct position. Hope that helps.

Update: From the official documentation on topLayoutGuide

A view controller within a container view controller does not set this property's value. Instead, the container view controller constrains the value to indicate: The bottom of the navigation bar, if a navigation bar is visible The bottom of the status bar, if only a status bar is visible The top edge of the view controller’s view, if neither a status bar nor navigation bar is visible

So the container view needs to implement correct constraints and hide/show bars and such for the effects to work. AFAIK there is no API to do this in custom container view controllers.

0
votes

I found a way. First uncheck "Extend Edges" property of controller after that navigation bar will get in dark color. Add a view to controller and set top and bottom LayoutConstraint -100. Then make view's clipsubview property no (for navigaionbar transculent effect). My english is bad sorry for that. :)