子视图控制器中的topLayoutGuide


70

我有一个UIPageViewController带有半透明状态栏和导航栏。topLayoutGuide如预期的那样,它是64像素。

但是,即使子视图控制器显示在状态栏和导航栏下,它们的子视图控制器也会UIPageViewController报告topLayoutGuide0像素。

这是预期的行为吗?如果是这样,将子视图控制器的视图放置在real下的最佳方法是topLayoutGuide什么?

(没有使用parentViewController.topLayoutGuide,我认为这是一个hack)


3
我想知道它们是否还没有topLayoutGuide为嵌套ViewController包含实现足够的实现。提出一个问题,即如果实现自定义容器,人们将如何处理它……
Mike Pollard

1
您确定要在viewWillLayoutSubviews或viewDidLayoutSubviews中测试topLayoutGuide属性吗?如果我没记错的话,就不能保证在viewWillAppear / viewDidAppear方法中返回正确的值……
Carlos P

2
我正在进行测试viewWillLayoutSubviews,是的。
hpique

是的,我也没有找到解决方案。子视图控制器和扩展边缘存在许多错误。iOS7尚未准备就绪。
Leo Natan 2013年

3
我认为肯定有一些时髦的事情。我有一个带有UITabbarController的UINavigationController。最初选择的选项卡始终具有正确的间距,以显示在导航栏下方。但是,当切换到其他选项卡时,顶部的单元格会显示在顶部栏的下方。
斯蒂芬·达林顿

Answers:


49

尽管这个答案可能是正确的,但我仍然发现自己必须向上移动包含树以找到正确的父视图控制器并获得您所描述的“真实topLayoutGuide”。这样我可以手动实现automaticallyAdjustsScrollViewInsets

这就是我的做法:

在我的表视图控制器(UIViewController实际上是其子类)中,我有以下内容:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    _tableView.frame = self.view.bounds;

    const UIEdgeInsets insets = (self.automaticallyAdjustsScrollViewInsets) ? UIEdgeInsetsMake(self.ms_navigationBarTopLayoutGuide.length,
                                                                                               0.0,
                                                                                               self.ms_navigationBarBottomLayoutGuide.length,
                                                                                               0.0) : UIEdgeInsetsZero;
    _tableView.contentInset = _tableView.scrollIndicatorInsets = insets;
}

注意中的category方法UIViewController,这是我实现它们的方式:

@implementation UIViewController (MSLayoutSupport)

- (id<UILayoutSupport>)ms_navigationBarTopLayoutGuide {
    if (self.parentViewController &&
        ![self.parentViewController isKindOfClass:UINavigationController.class]) {
        return self.parentViewController.ms_navigationBarTopLayoutGuide;
    } else {
        return self.topLayoutGuide;
    }
}

- (id<UILayoutSupport>)ms_navigationBarBottomLayoutGuide {
    if (self.parentViewController &&
        ![self.parentViewController isKindOfClass:UINavigationController.class]) {
        return self.parentViewController.ms_navigationBarBottomLayoutGuide;
    } else {
        return self.bottomLayoutGuide;
    }
}

@end

希望这可以帮助 :)


1
我希望这个答案被投票给inifitum ...我去了,并在懒惰的开发人员编写的应用程序中删除了10个错误的硬编码值的地方。错误已解决,不再需要硬编码。
Mazyod 2014年

好答案,谢谢。只是为了使事情变得纯净:UIEdgeInsets值的CGFloat值不是double类型。我最好将它们设置为0.f或0.0f而不是0.0 :)。
Vive 2014年

在99%的情况下,此方法非常有效。但是如果您有一个UINavigationController作为childViewController,则不会。
Loadex 2015年

2
谢谢。我必须添加一个 [_tableView setContentOffset:CGPointMake(0, 0 - _tableView.contentInset.top) animated:NO];使其正确滚动到顶部
Conor 2015年

这简直让我头疼:)
蒂姆·约翰森

11

我可能是错的,但我认为这种行为是正确的。容器视图控制器可以使用topLayout值来布局其视图的子视图。

参考资料说:

要在不使用约束的情况下使用顶部布局参考线,请获取该参考线相对于包含视图的顶部边界的位置。

在父级中,相对于包含视图,该值为64。

在子级中,相对于包含视图(父级),该值为0。

在容器View Controller中,您可以通过以下方式使用属性:

- (void) viewWillLayoutSubviews {

    CGRect viewBounds = self.view.bounds;
    CGFloat topBarOffset = self.topLayoutGuide.length;

    for (UIView *view in [self.view subviews]){
        view.frame = CGRectMake(viewBounds.origin.x, viewBounds.origin.y+topBarOffset, viewBounds.size.width, viewBounds.size.height-topBarOffset);
    }
}

子视图控制器不需要知道有导航栏和状态栏:考虑到它的父视图,子视图控制器已经布局好了子视图。

如果我创建一个基于页面的新项目,将其嵌入到导航控制器中,然后将此代码添加到父视图控制器中,则看起来工作正常:

在此处输入图片说明


19
但是,如果子视图控制器是UIScrollView子类,则如果设置这样的框架,则不会获得内容显示在半透明导航栏下的效果,这首先是要使用半透明导航栏的全部目的。 。
kmikael

你不叫超级,这很整齐
哈哈

如果有人使用页面控制器,并且有人对页面视图添加了约束,则该解决方案适用于页面控制器,这对我来说
Majid Bashir

11

您可以在情节提要中添加约束,并在viewWillLayoutSubviews中进行更改

像这样的东西:

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
    self.topGuideConstraint.constant = [self.parentViewController.topLayoutGuide length];
}

1
如果要在导航栏下扩展边缘,则没有任何意义。
pronebird 2014年

这个答案解决了我的子视图控制器中没有导航栏时最重要的布局指南问题。Swift 3版本:override func viewWillLayoutSubviews() {self.topGuideConstraint.constant = (self.parent ?? self).topLayoutGuide.length}
LOP_Luke


3

如果您有UIPageViewController像OP这样的功能,并且您有例如集合视图控制器作为子级。事实证明,内容插入的修复很简单,并且可以在iOS 8上运行:

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    UIEdgeInsets insets = self.collectionView.contentInset;
    insets.top = self.parentViewController.topLayoutGuide.length;
    self.collectionView.contentInset = insets;
    self.collectionView.scrollIndicatorInsets = insets;
}

2

这已在iOS 8中解决。

如何为子视图控制器设置topLayoutGuide位置

本质上,容器视图控制器应该(top|bottom|left|right)LayoutGuide像其他任何视图一样约束子视图控制器。(在iOS 7中,它已经完全按照所需的优先级进行了约束,因此无法正常工作。)


想要共享一些代码吗?当谈到基于约束的布局时,我的头颅很厚:)
StianHøiland2014年

实际上,只要您先在版式指南中删除现有约束,然后再添加自己的约束,这在iOS 7中同样适用。
Archaeopterasa

@Archaeopterasa您能详细说明一下吗?您要指的是布局指南中的哪些现有限制?
佩塔尔

@Jesse Rusak究竟发生了什么变化?我们如何才能对运行iOS 7的应用程序进行相同的更改以实现相同的行为。:我已经为这个具体问题stackoverflow.com/questions/28024425/...
斯托

@ pe60t0可以删除自动添加的约束(在调试输出中显示为_UILayoutSupportConstraint而不是NSLayoutConstraint的约束)。在我的代码中,我想约束子视图控制器的布局指南以匹配父视图控制器的布局指南,因此我首先浏览了约束列表,并删除了设置其高度的约束列表,然后能够像普通视图一样对其进行约束。(捕获,因为总是在自动布局中有一个:方向更改可能很混乱,并且需要仔细的操作顺序。)
Archaeopterasa 2015年

1

我认为这些指南绝对是为嵌套子控制器设置的。例如,假设您有:

  • 100x50的屏幕,顶部有20像素状态栏。
  • 覆盖整个窗口的顶级视图控制器。其topLayoutGuide为20。
  • 顶视图内的嵌套视图控制器覆盖底部95个像素,例如。从屏幕顶部向下5个像素。此视图的topLayoutGuide应该为15,因为状态栏覆盖了它的前15个像素。

那将是有道理的:这意味着嵌套视图控制器可以设置约束以防止不必要的重叠,就像顶级控制器一样。不必担心它是嵌套的,也不用担心它的父对象在屏幕上的显示位置,并且父视图控制器不需要知道孩子如何与状态栏进行交互。

这似乎也是文档(或至少某些文档)所说的:

顶部布局指南以点为单位指示视图控制器视图的顶部与覆盖该视图的最底部栏的底部之间的距离(以磅为单位)

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UILayoutSupport_Protocol/Reference/Reference.html

但这并没有说明仅适用于顶级视图控制器。

但是,我不知道这是否真的发生。我肯定已经看到了非零topLayoutGuides的子视图控制器,但是我仍然在弄怪。(在我的情况下,最高指南应该为零,因为视图不在屏幕顶部,这就是我现在要撞到的地方...)


0

这是已知导向长度的方法。创建约束,而不是对辅助线,而是使用固定常数(假定辅助线距离将是)来约束视图的顶部。


0

@NachoSoto的快速实现答案:

extension UIViewController {

    func navigationBarTopLayoutGuide() -> UILayoutSupport {
        if let parentViewController = self.parentViewController {
            if !parentViewController.isKindOfClass(UINavigationController) {
                return parentViewController.navigationBarTopLayoutGuide()
            }
        }

        return self.topLayoutGuide
    }

    func navigationBarBottomLayoutGuide() -> UILayoutSupport {
        if let parentViewController = self.parentViewController {
            if !parentViewController.isKindOfClass(UINavigationController) {
                return parentViewController.navigationBarBottomLayoutGuide()
            }
        }

        return self.bottomLayoutGuide
    }
}

0

就像我几分钟前一样,不确定是否有人仍然对此有疑问。
我的问题是这样的(来自https://knuspermagier.de/2014-fixing-uipageviewcontrollers-top-layout-guide-problems.html的gif源)。
简而言之,我的pageViewController有3个子ViewController。第一个viewcontroller很好,但是当我滑动到下一个时,整个视图会错误地偏移到顶部(我猜是20像素左右),但是当手指离开屏幕时它将恢复正常。
我整夜熬夜寻找解决方案,但是仍然找不到运气。然后突然我想到了这个疯狂的主意:

[pageViewController setViewControllers:@[listViewControllers[1]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished) {

}];

[pageViewController setViewControllers:@[listViewControllers[0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {

}];

我的listViewControllers有3个子ViewController。索引0处的那个有问题,因此我首先将其设置为pageviewcontroller的根,然后立即将其设置回第一个视图控制器(正如我预期的那样)。瞧,行得通!
希望能帮助到你!


0

这是一种不幸的行为,似乎已在iOS 11中通过安全区域API改正得到纠正。也就是说,您将始终从根视图控制器获得正确的值。例如,如果要在iOS 11之前的安全区域上限高度:

斯威夫特4

let root = UIApplication.shared.keyWindow!.rootViewController!
let topLayoutGuideLength = root.topLayoutGuide.length
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.