Planning Guide for the Top Level of the Navigation Controller Not Implemented Using Custom Navigation

Short version:

I had a problem with the layout guide for the top layout when used in conjunction with a custom transition and UINavigationController in iOS7. In particular, the restriction between the top layout and the text is not met. Has anyone encountered this issue?




Long version:

I have a scene that uniquely identifies constraints (i.e., top, bottom, left, and right) that displays the view as follows:

right

But when I use this with a custom transition on the navigation controller, the top restriction in the top layout guide seems to be turned off, and it appears as if the top layout were at the top of the screen, and not at the bottom of the navigation controller:

wrong

It seems that the "layout guide" with the navigation controller is confused when using a custom transition. Other restrictions apply correctly. And if I rotate the device and rotate it again, everything will suddenly display correctly, so it does not seem that the restrictions are not defined properly. Similarly, when I turn off my custom transition, the views are displayed correctly.

Having said that _autolayoutTrace reports that the UILayoutGuide objects UILayoutGuide suffering from AMBIGUOUS LAYOUT when I run:

 (lldb) po [[UIWindow keyWindow] _autolayoutTrace] 

But these layout guides are always reported as ambiguous when I look at them, although I guaranteed no missing restrictions (I made the usual choice of the view controller and selecting "Add missing restrictions for the view controller", or select all the controls and do the same for them).

In terms of how accurately I am making the transition, I specified an object that matches the UIViewControllerAnimatedTransitioning in the animationControllerForOperation method:

 - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController*)fromVC toViewController:(UIViewController*)toVC { if (operation == UINavigationControllerOperationPush) return [[PushAnimator alloc] init]; return nil; } 

and

 @implementation PushAnimator - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext { return 0.5; } - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:toViewController.view]; CGFloat width = fromViewController.view.frame.size.width; toViewController.view.transform = CGAffineTransformMakeTranslation(width, 0); [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ fromViewController.view.transform = CGAffineTransformMakeTranslation(-width / 2.0, 0); toViewController.view.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { fromViewController.view.transform = CGAffineTransformIdentity; [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end 

I also did the above by setting the frame view, not transform , with the same result.

I also tried to manually verify that the restrictions were layoutIfNeeded calling layoutIfNeeded . I also tried setNeedsUpdateConstraints , setNeedsLayout etc.

On the bottom line, did anyone successfully marry a navigation controller custom transition with restrictions using the placement guide?

+60
autolayout ios7 uinavigationcontroller custom-transition
Dec 01 '13 at 13:44
source share
11 answers

I solved this by setting a height limit on topLayoutGuide . Setting edgesForExtendedLayout not impossible for me, since I needed the navigation bar to be hidden in the view mode, and also to be able to select layouts using topLayoutGuide .

A direct check of the limits in the game shows that iOS adds a height limit to topLayoutGuide with a value equal to the height of the navigation bar of the navigation controller. Also, in iOS 7, using a custom animation transition leaves a limit with a height of 0. They fixed it in iOS 8.

This is the solution I came up with to fix the restriction (this is in Swift, but the equivalent should work in Obj-C). I tested that it works on iOS 7 and 8.

 func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view let destinationVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! destinationVC.view.frame = transitionContext.finalFrameForViewController(destinationVC) let container = transitionContext.containerView() container.addSubview(destinationVC.view) // Custom transitions break topLayoutGuide in iOS 7, fix its constraint if let navController = destinationVC.navigationController { for constraint in destinationVC.view.constraints() as [NSLayoutConstraint] { if constraint.firstItem === destinationVC.topLayoutGuide && constraint.firstAttribute == .Height && constraint.secondItem == nil && constraint.constant == 0 { constraint.constant = navController.navigationBar.frame.height } } } // Perform your transition animation here ... } 
+15
Feb 17 '15 at 19:47
source share

I managed to fix the problem by adding this line:

 toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController]; 

To:

 - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView { // Add the toView to the container UIView* containerView = [transitionContext containerView]; [containerView addSubview:toView]; [containerView sendSubviewToBack:toView]; // animate toVC.view.frame = [transitionContext finalFrameForViewController:toVC]; NSTimeInterval duration = [self transitionDuration:transitionContext]; [UIView animateWithDuration:duration animations:^{ fromView.alpha = 0.0; } completion:^(BOOL finished) { if ([transitionContext transitionWasCancelled]) { fromView.alpha = 1.0; } else { // reset from- view to its original state [fromView removeFromSuperview]; fromView.alpha = 1.0; } [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } 

From the Apple documentation for [finalFrameForViewController]: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerContextTransitioning_protocol/#//apple_ref/occ/intfm/UIViewControllerContextTransitioning/finalFrameForControl

+25
Oct 06 '15 at 21:14
source share

I struggled with the same problem. Putting this in the viewDidLoad of my toViewController really helped me:

 self.edgesForExtendedLayout = UIRectEdgeNone; 

This did not solve all my problems, and I'm still looking for a better approach, but it certainly made it a little easier.

+10
Dec 03 '13 at 20:04 on
source share

FYI, I ended up using Alex's answer option, programmatically changing the height constraint constant of the top vertex of the layout in the animateTransition method. I just post this to share the Objective-C version (and eliminate the constant == 0 test).

 CGFloat navigationBarHeight = toViewController.navigationController.navigationBar.frame.size.height; for (NSLayoutConstraint *constraint in toViewController.view.constraints) { if (constraint.firstItem == toViewController.topLayoutGuide && constraint.firstAttribute == NSLayoutAttributeHeight && constraint.secondItem == nil && constraint.constant < navigationBarHeight) { constraint.constant += navigationBarHeight; } } 

Thanks, Alex.

+4
Feb 22 '15 at 16:50
source share

Just put the following code in viewDidLoad

 self.extendedLayoutIncludesOpaqueBars = YES; 
+4
Sep 21 '15 at 9:43 on
source share

As @Rob mentioned, topLayoutGuide not reliable when using custom transitions in the UINavigationController . I worked on this using my own layout. You can see the code in action in a demo project. Main characteristics:

Category for custom layout guides:

 @implementation UIViewController (hp_layoutGuideFix) - (BOOL)hp_usesTopLayoutGuideInConstraints { return NO; } - (id<UILayoutSupport>)hp_topLayoutGuide { id<UILayoutSupport> object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide)); return object ? : self.topLayoutGuide; } - (void)setHp_topLayoutGuide:(id<UILayoutSupport>)hp_topLayoutGuide { HPLayoutSupport *object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide)); if (object != nil && self.hp_usesTopLayoutGuideInConstraints) { [object removeFromSuperview]; } HPLayoutSupport *layoutGuide = [[HPLayoutSupport alloc] initWithLength:hp_topLayoutGuide.length]; if (self.hp_usesTopLayoutGuideInConstraints) { [self.view addSubview:layoutGuide]; } objc_setAssociatedObject(self, @selector(hp_topLayoutGuide), layoutGuide, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end 

HPLayoutSupport is a class that will act as a layout guide. It must be a subclass of UIView to avoid crashes (I wonder why this is not part of the UILayoutSupport interface).

 @implementation HPLayoutSupport { CGFloat _length; } - (id)initWithLength:(CGFloat)length { self = [super init]; if (self) { self.translatesAutoresizingMaskIntoConstraints = NO; self.userInteractionEnabled = NO; _length = length; } return self; } - (CGSize)intrinsicContentSize { return CGSizeMake(1, _length); } - (CGFloat)length { return _length; } @end 

UINavigationControllerDelegate is responsible for β€œfixing” the layout guide before going:

 - (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { toVC.hp_topLayoutGuide = fromVC.hp_topLayoutGuide; id <UIViewControllerAnimatedTransitioning> animator; // Initialise animator return animator; } 

Finally, the UIViewController uses hp_topLayoutGuide instead of topLayoutGuide in the constraints and indicates this by overriding hp_usesTopLayoutGuideInConstraints :

 - (void)updateViewConstraints { [super updateViewConstraints]; id<UILayoutSupport> topLayoutGuide = self.hp_topLayoutGuide; // Example constraint NSDictionary *views = NSDictionaryOfVariableBindings(_imageView, _dateLabel, topLayoutGuide); NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topLayoutGuide][_imageView(240)]-8-[_dateLabel]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]; [self.view addConstraints:constraints]; } - (BOOL)hp_usesTopLayoutGuideInConstraints { return YES; } 

Hope this helps.

+3
Feb 26 '14 at 0:44
source share

I found a way. First uncheck the "Extend Edges" property of the controller. After that, the navigation bar turns dark. Add a view to the controller and set the top and bottom LayoutConstraint -100. Then create the viewviewviewview no property (for the translucent effect of the navigaionbar). My English is a bad litter for this. :)

+2
Jun 23 '14 at 6:29
source share

I had the same problem, as a result I created my own topLayout guide and made restrictions for it, not topLayoutGuide. Not perfect. Only post it here if someone is stuck and looking for a quick hacking solution http://www.github.com/stringcode86/SCTopLayoutGuide

+1
Apr 6 '14 at 11:25
source share

try:

 self.edgesforextendedlayout=UIRectEdgeNone 

Or just adjust the navigation bar opaque and set the background image or backgroundcolor to the navigation bar

0
Jan 24 '14 at 3:50
source share

Here, the simple solution that I use works fine for me: during the setup phase - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext manually set "from" and "to" viewController.view.frame.origin.y = navigationController .navigationBar.frame.size.height. This will make your layout views automatically position themselves vertically as you expect.

Minus the pseudocode (for example, you probably have your own way of determining if the iOS7 device is working), here is what my method looks like:

 - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *container = [transitionContext containerView]; CGAffineTransform destinationTransform; UIViewController *targetVC; CGFloat adjustmentForIOS7AutoLayoutBug = 0.0f; // We're doing a view controller POP if(self.isViewControllerPop) { targetVC = fromViewController; [container insertSubview:toViewController.view belowSubview:fromViewController.view]; // Only need this auto layout hack in iOS7; it fixed in iOS8 if(_device_is_running_iOS7_) { adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height; [toViewController.view setFrameOriginY:adjustmentForIOS7AutoLayoutBug]; } destinationTransform = CGAffineTransformMakeTranslation(fromViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug); } // We're doing a view controller PUSH else { targetVC = toViewController; [container addSubview:toViewController.view]; // Only need this auto layout hack in iOS7; it fixed in iOS8 if(_device_is_running_iOS7_) { adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height; } toViewController.view.transform = CGAffineTransformMakeTranslation(toViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug); destinationTransform = CGAffineTransformMakeTranslation(0.0f,adjustmentForIOS7AutoLayoutBug); } [UIView animateWithDuration:_animation_duration_ delay:_animation_delay_if_you_need_one_ options:([transitionContext isInteractive] ? UIViewAnimationOptionCurveLinear : UIViewAnimationOptionCurveEaseOut) animations:^(void) { targetVC.view.transform = destinationTransform; } completion:^(BOOL finished) { [transitionContext completeTransition:([transitionContext transitionWasCancelled] ? NO : YES)]; }]; } 

A few bonuses in this example:

  • In order to control the controller, this custom transition will shift the pressed toViewController.view over the stationary fromViewController.view. For pops, fromViewController.view slides to the right and shows a fixed toViewController.view below it. Overall, this is just a subtle twist on the output of the iOS7 + controller.
  • The completion block [UIView animateWithDuration:...] shows the correct way to handle completed and canceled user transitions. This tiny tidbit was a classic moment for the head; hope this helps someone else.

Finally, I would like to note that, as far as I can tell, this is only a problem with iOS7, fixed in iOS8: my user transition with the view controller, which is broken in iOS7, works fine in iOS8 without modification. In this case, you must make sure that this is what you also see, and if so, just run the patch on devices running iOS7.x. As you can see in the above code example, the y-setting value is 0.0f if the device does not work iOS7.x.

0
Oct 06 '14 at 23:48
source share

In the storyboard, add another vertical constraint to the main top view. I have the same problem, but adding this constraint helps me avoid manual constraints. See screenshot here link

Another solution is to calculate the VC framework ... something like this:

 float y = toVC.navigationController.navigationBar.frame.origin.y + toVC.navigationController.navigationBar.frame.size.height; toVC.view.frame = CGRectMake(0, y, toVC.view.frame.size.width, toVC.view.frame.size.height - y); 

Let me know if you have found the best solution. I am also struggling with this problem, and I came up with previous ideas.

-one
Jul 15 '14 at 2:32
source share



All Articles