导航控制器顶部布局指南不符合自定义过渡

简洁版本:

在自定义转换和iOS7中的UINavigationController中使用自动布局顶部布局指南时遇到问题。 具体而言,顶部布局指南和文本视图之间的约束不被遵守。 有没有人遇到过这个问题?


长版本:

我有一个明确定义约束的场景(即顶部,底部,左侧和右侧),呈现如下所示的视图:

对

但是,当我在导航控制器上使用这个自定义转换时,顶部布局指南的顶部约束似乎closures,呈现如下,就好像顶部布局指南位于屏幕顶部而不是底部的导航控制器:

错误

在使用自定义转换时,导航控制器的“顶层布局指南”会变得混乱。 剩下的约束正在正确应用。 如果我旋转设备并再次旋转,一切都会突然呈现正确,所以看起来不是约束没有被正确定义的问题。 同样,当我closures我的自定义转换时,视图呈现正确。

话虽如此, _autolayoutTrace报告说, UILayoutGuide对象遭受AMBIGUOUS LAYOUT ,当我运行:

 (lldb) po [[UIWindow keyWindow] _autolayoutTrace] 

但是,即使我已经确保没有缺less约束(我已经完成了习惯select视图控制器并select“为视图控制器添加缺less的约束”或select全部的控制,并为他们做同样的)。

就我如何精确转换而言,我已经在animationControllerForOperation方法中指定了一个符合UIViewControllerAnimatedTransitioning的对象:

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

 @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 

我也做了上述的再现,设置视图的frame ,而不是transform ,具有相同的结果。

我也尝试手动确保通过调用layoutIfNeeded来重新应用约束。 我也试过setNeedsUpdateConstraintssetNeedsLayout

底线,有没有人成功地结婚导航控制器的自定义过渡约束,使用顶级布局指南?

我通过固定topLayoutGuide的高度约束来解决这个问题。 调整edgesForExtendedLayout不是我的select,因为我需要目标视图,以导航栏下,而且还能够使用topLayoutGuide布局子视图。

直接检查游戏中的约束表明,iOS向topLayoutGuide添加了一个高度约束,其值等于导航控制器导航栏的高度。 除了在iOS 7中,使用自定义animation转换离开高度为0的约束,他们在iOS 8中解决了这个问题。

这是我想出的解决scheme(这是在Swift中,但相当于Obj-C)的解决scheme。 我已经testing过它可以在iOS 7和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 ... } 

通过添加这一行来解决我的问题:

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

至:

 - (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]]; }]; } 

从Apple的[finalFrameForViewController]文档: https : //developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerContextTransitioning_protocol/#//apple_ref/occ/intfm/UIViewControllerContextTransitioning/finalFrameForViewController :

我挣扎于完全相同的问题。 把它放在我的toViewController的viewDidLoad真的帮助我:

 self.edgesForExtendedLayout = UIRectEdgeNone; 

这并没有解决我所有的问题,我仍然在寻找一个更好的方法,但这当然使它更容易一些。

仅供参考,我最终采用Alex的答案的变化,编程方式更改animateTransition方法中顶部布局指南的高度约束常数。 我只发布这个共享Objective-C的翻译(并消除constant == 0testing)。

 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; } } 

谢谢,Alex。

只要把下面的代码放到viewDidLoad

 self.extendedLayoutIncludesOpaqueBars = YES; 

正如@Rob提到的,在UINavigationController使用自定义转换时, topLayoutGuide不可靠。 我通过使用我自己的布局指南解决这个问题。 您可以在此演示项目中看到正在执行的代码。 强调:

自定义布局指南的类别:

 @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是将充当布局指南的类。 它必须是一个UIView子类,以避免崩溃(我想知道为什么这不是UILayoutSupport接口的一部分)。

 @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是负责在转换之前“修复”布局指南的人员:

 - (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; } 

最后, UIViewController在约束中使用hp_topLayoutGuide而不是topLayoutGuide ,并通过覆盖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; } 

希望能帮助到你。

我find了方法。 首先取消选中控制器的“Extend Edges”属性。 之后,导航栏变暗色。 将视图添加到控制器并设置顶部和底部LayoutConstraint -100。 然后使视图的clipubview属性no(对于navigaionbar transculent effect)。 我的英语不好, 🙂

我有同样的问题,最终实现了我自己的topLayout指南视图,并制定约束,而不是topLayoutGuide。 不理想。 只有在这里张贴,以防止有人被卡住,寻找快速的哈克解决schemehttp://www.github.com/stringcode86/SCTopLayoutGuide

尝试:

 self.edgesforextendedlayout=UIRectEdgeNone 

或者只是设置导航栏不透明,并将背景图像或背景颜色设置为导航栏

以下是我使用的简单解决scheme:在设置阶段- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext ,手动设置你的“from”和“to”viewController.view.frame.origin .y = navigationController.navigationBar.frame.size.height。 它会使您的自动布局视图按照您的预期垂直放置。

减去伪代码(例如,你可能有自己的方式来确定一个设备是否运行iOS7),这是我的方法看起来像:

 - (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's 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's 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)]; }]; } 

关于这个例子的一些奖金:

  • 对于视图控制器推送,这个自定义转换将推送到ViewController.view的顶部的ViewViewController.view。 对于popup窗口,从ViewController.view滑到右侧,并显示一个unmving toViewController.view下。 总而言之,这只是股票iOS7 +视图控制器转换的微妙转折。
  • [UIView animateWithDuration:...]完成块显示处理完成和取消的自定义转换的正确方法。 这个小小的珍闻是一个典型的头巴掌时刻, 希望它能帮助别人。

最后,我想指出的是,据我所知,这是一个在iOS8中已经修复的仅iOS7问题:iOS7中的自定义视图控制器转换在iOS8中工作得很好,无需修改。 这就是说,你应该validation这是你所看到的,如果是这样,只运行修复运行iOS7.x的设备上。 正如您在上面的代码示例中所看到的,除非设备运行iOS7.x,否则y-调整值为0.0f。

在故事板中,向主视图顶部添加另一个垂直限制。 我也有同样的问题,但添加约束帮助我避免手动约束。 看这里的截图链接

其他解决scheme是计算toVC框架…这样的事情:

 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); 

让我知道你是否find了更好的解决scheme。 我一直在努力解决这个问题,并提出了以前的想法。