topLayoutGuide in child view controller

2019-01-16 03:42发布

I have a UIPageViewController with translucent status bar and navigation bar. Its topLayoutGuide is 64 pixels, as expected.

However, the child view controllers of the UIPageViewController report a topLayoutGuide of 0 pixels, even if they're shown under the status bar and navigation bar.

Is this the expected behavior? If so, what's the best way to position a view of a child view controller under the real topLayoutGuide?

(short of using parentViewController.topLayoutGuide, which I'd consider a hack)

11条回答
混吃等死
2楼-- · 2019-01-16 03:45

I think the guides are definitely meant to be set for nested child controllers. For example, suppose you have:

  • A 100x50 screen, with a 20 pixel status bar at the top.
  • A top-level view controller, covering the whole window. Its topLayoutGuide is 20.
  • A nested view controller inside the top view covering the bottom 95 pixels, eg. 5 pixels down from the top of the screen. This view should have a topLayoutGuide of 15, since its top 15 pixels are covered by the status bar.

That would make sense: it means that the nested view controller can set constraints to prevent unwanted overlap, just like a top-level one. It doesn't have to care that it's nested, or where on the screen its parent is displaying it, and the parent view controller doesn't need to know how the child wants to interact with the status bar.

That also seems to be what the documentation--or some of the documentation, at least--says:

The top layout guide indicates the distance, in points, between the top of a view controller’s view and the bottom of the bottommost bar that overlays the view

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

That doesn't say anything about only working for top-level view controllers.

But, I don't know if this is what actually happens. I've definitely seen child view controllers with nonzero topLayoutGuides, but I'm still figuring out the quirks. (In my case the top guide should be zero, since the view isn't at the top of the screen, which is what I'm banging my head against at the moment...)

查看更多
啃猪蹄的小仙女
3楼-- · 2019-01-16 03:45

This is an unfortunate behavior that appears to have been rectified in iOS 11 with the safe-area API revamp. That said, you will always get the correct value off the root view controller. For example, if you want the upper safe area height pre-iOS 11:

Swift 4

let root = UIApplication.shared.keyWindow!.rootViewController!
let topLayoutGuideLength = root.topLayoutGuide.length
查看更多
一纸荒年 Trace。
4楼-- · 2019-01-16 03:47

The documentation says to use topLayoutGuide in viewDidLayoutSubviews if you are using a UIViewController subclass, or layoutSubviews if you are using a UIView subclass.

If you use it in those methods you should get an appropriate non-zero value.

Documentation link: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instp/UIViewController/topLayoutGuide

查看更多
We Are One
5楼-- · 2019-01-16 03:50

This has been addressed in iOS 8.

How to set topLayoutGuide position for child view controller

Essentially, the container view controller should constrain the child view controller's (top|bottom|left|right)LayoutGuide as it would any other view. (In iOS 7, it was already fully constrained at a required priority, so this didn't work.)

查看更多
太酷不给撩
6楼-- · 2019-01-16 03:55

In case if you have UIPageViewController like OP does and you have for example collection view controllers as children. Turns out the fix for content inset is simple and it works on 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;
}
查看更多
走好不送
7楼-- · 2019-01-16 03:59

Swifty implementation of @NachoSoto answer:

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
    }
}
查看更多
登录 后发表回答