25

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?

alex da franca
  • 1,450
  • 2
  • 15
  • 27
  • 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 Nov 16 '13 at 03:10
  • 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 Nov 17 '13 at 10:59
  • 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 https://github.com/stringcode86/SCTopLayoutGuide – stringCode Apr 06 '14 at 11:22
  • 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 Apr 08 '14 at 16:58
  • possible duplicate of [Navigation controller top layout guide not honored with custom transition](http://stackoverflow.com/questions/20312765/navigation-controller-top-layout-guide-not-honored-with-custom-transition) – Alex Pretzlav Feb 17 '15 at 19:54

4 Answers4

5

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.

Loukas
  • 616
  • 1
  • 6
  • 22
EBM
  • 61
  • 1
  • 5
  • 1
    No, it doesn't in my case. Are you using a custom transition controller? I tried setNeedsLayout all over the place, but it doesn't fix the problem. – alex da franca Apr 21 '14 at 11:17
  • 1
    I have a better solution! I got the same issue, and after spelunking for a second I found the "Extend Edges" checkboxes section on the pertinent view controller, in the Attribute Inspector. I unchecked "Under Bottom Bars" and friends, et voila! The layout now works automatically again. – lericson May 27 '14 at 18:05
  • 2
    This is actually the right answer, except you have to make sure the view is actually displayed. You must use the viewDidAppear: method instead of viewWillAppear:. This because the UINavigationBar will be fully set up when this method is called (and your transition will only call the completion handler at this point). – Tiago Fael Matos Jul 18 '14 at 00:04
  • I put this code in didAppear but it works with slide animation not update ui immediately .. any solution for this ? Thank You – Yogesh Patel Oct 29 '19 at 07:01
0

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.

Loukas
  • 616
  • 1
  • 6
  • 22
Dan Jackson
  • 451
  • 3
  • 5
  • Ui, that would be too much asked for me to stuff everything into a scrollview just for the purpose of avoiding to run into the issue. And yes not extending under the navigation bar is what I am doing now, but sacrifice the transparent navigation bar, which I would otherwise like to be transparent. – alex da franca Nov 19 '13 at 17:16
  • (...ups, hit return by mistake) my problem is that somehow the navigationcontroller does not do the top layout guide update, when using a custom view controller transition as opposed to use the standard, built-in, view controller push transition. – alex da franca Nov 19 '13 at 18:04
  • 1
    As for the opening the storyboard in a text editor: You shouldn't need to do that. Just delete the constraint, which is attached to the "wrong" layout guide and create a new one using the "pin" menu at the bottom right of IB. In that "pin" menu you have a drop down menu in order to choose to which other item you want to attach the constraint. There choose your superview instead of "top layout guide". – alex da franca Nov 19 '13 at 18:08
0

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.

jgarth
  • 61
  • 5
  • Got any sample code that shows how & where you added the new constraints? I'm wondering if you did it during initial setup of your view hierarchy, or during the custom VC transition process. – John Jacecko Jul 14 '14 at 00:54
  • I'm using a storyboard – my setup has an initial view controller that's filled by my custom navigation controller's view via embed segue. This I did in Interface Builder, so **they are set up before any transitioning happens**, during the initial setup of my view hierarchy. I think you shouldn't need to add constraints during transitioning, since the screen size wouldn't change in the middle of running your app (other than reorientation). Hope this helps. – jgarth Jul 25 '14 at 10:41
0

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. :)

Chitra Khatri
  • 1,260
  • 2
  • 14
  • 31
Yusuf terzi
  • 186
  • 7