子视图控制器中的topLayoutGuide

时间:2013-10-02 15:20:12

标签: ios uiviewcontroller ios7 autolayout uipageviewcontroller

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

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

这是预期的行为吗?如果是这样,在真实topLayoutGuide下定位子视图控制器视图的最佳方法是什么?

(没有使用parentViewController.topLayoutGuide,我认为这是黑客攻击)

11 个答案:

答案 0 :(得分:48)

虽然this answer可能是正确的,但我仍然发现自己不得不前往收容树找到合适的父视图控制器并获得您所描述的“真实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;
}

注意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 :(得分:11)

您可以在故事板中添加约束并在viewWillLayoutSubviews

中更改它

类似的东西:

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

答案 2 :(得分:10)

我可能错了,但在我看来,行为是正确的。容器视图控制器可以使用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);
    }
}

子视图控制器不需要知道存在导航和状态栏:其父级已经将其考虑在内的子视图。

如果我创建一个新的基于页面的项目,将其嵌入导航控制器,并将此代码添加到父视图控制器,它似乎工作正常:

enter image description here

答案 3 :(得分:8)

文档说如果你使用的是UIViewController子类,则在viewDidLayoutSubviews中使用topLayoutGuide,如果你使用的是UIView子类,则使用layoutSubviews。

如果在这些方法中使用它,则应获得适当的非零值。

文档链接: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/topLayoutGuide

答案 4 :(得分: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;
}

答案 5 :(得分:2)

这已在iOS 8中解决。

How to set topLayoutGuide position for child view controller

基本上,容器视图控制器应该像任何其他视图一样约束子视图控制器(top|bottom|left|right)LayoutGuide。 (在iOS 7中,它已经完全受限于所需的优先级,因此这不起作用。)

答案 6 :(得分: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的子视图控制器,但我仍然在弄清怪癖。 (在我的情况下,顶部指南应该为零,因为视图不在屏幕的顶部,这就是我现在正在猛烈抨击...)

答案 7 :(得分:0)

这是已知指导长度的方法。创建约束不是指南,而是使用固定常量查看顶部,假定引导距离为。

答案 8 :(得分: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
    }
}

答案 9 :(得分:0)

不确定是否有人仍然遇到此问题,因为我几分钟前仍然这样做 我的问题是like this(来自https://knuspermagier.de/2014-fixing-uipageviewcontrollers-top-layout-guide-problems.html的来源gif)。
简而言之,我的pageViewController有3个子视图控制器。第一个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个子视图控制器。索引0处的那个有问题,所以我首先将它设置为pageviewcontroller的根,然后将其设置为第一个视图控制器(正如我所料)。 瞧,它有效! 希望它有所帮助!

答案 10 :(得分:0)

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

Swift 4

let root = UIApplication.shared.keyWindow!.rootViewController!
let topLayoutGuideLength = root.topLayoutGuide.length