topLayoutGuide in child view controller
Solution 1
While this answer might be correct, I still found myself having to travel the containment tree up to find the right parent view controller and get what you describe as the "real topLayoutGuide
". This way I can manually implement automaticallyAdjustsScrollViewInsets
.
This is how I'm doing it:
In my table view controller (a subclass of UIViewController
actually), I have this:
- (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;
}
Notice the category methods in UIViewController
, this is how I implemented them:
@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
Hope this helps :)
Solution 2
you can add a constraint in the storyboard and change it in viewWillLayoutSubviews
something like this:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.topGuideConstraint.constant = [self.parentViewController.topLayoutGuide length];
}
Solution 3
I might be wrong, but in my opinion the behaviour is correct. The topLayout value can be used by the container view controller to layout its view's subviews.
The reference says:
To use a top layout guide without using constraints, obtain the guide’s position relative to the top bound of the containing view.
In the parent, relative to the containing view, the value will be 64.
In the child, relative to the containing view (the parent), the value will be 0.
In the container View Controller you could use the property this way:
- (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);
}
}
The Child view controller does not need to know that there are a Navigation and a Status bar : its parent will have already laid out its subviews taking that into account.
If I create a new page based project, embed it in a navigation controller, and add this code to the parent view controllers it seems to be working fine:
Solution 4
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
Solution 5
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;
}
hpique
iOS, Android & Mac developer. Founder of Robot Media. @hpique
Updated on July 05, 2022Comments
-
hpique almost 2 years
I have a
UIPageViewController
with translucent status bar and navigation bar. ItstopLayoutGuide
is 64 pixels, as expected.However, the child view controllers of the
UIPageViewController
report atopLayoutGuide
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) -
kmikael over 10 yearsBut if the child view controllers are
UIScrollView
subclasses, you don't get the effect of the content showing up under the translucent navigation bar, if you set the frame like this, which is the whole purpose of having a translucent navigation bar in the first place. -
Mazyod almost 10 yearsI hope this answer is voted to inifitum... I went and removed like 10 places of wrong hard coded values all over the app written by lazy devs. Bug solved, and no more hard-code-ness.
-
Stian Høiland over 9 yearsCare to share some code for this? I'm thick in the skull when it comes to constraint-based layout :)
-
Nat over 9 yearsGreat answer, thanks. Only to make things pure: UIEdgeInsets values are of CGFloat not double kind. I'd be better to set them as 0.f or 0.0f instead of 0.0 :).
-
pronebird over 9 yearsIt does not make sense if you want to extend edges under navigation bar.
-
Archaeopterasa over 9 yearsActually this works in iOS 7 too as long as you first remove the existing constraint(s) on the layout guide before adding your own.
-
Petar over 9 years@Archaeopterasa can you elaborate on this please ? What existing constraints on the layout guide are you referring to ?
-
Petar over 9 years@Jesse Rusak what exactly has been changed ? How can we apply the same change to apps running iOS 7 to achieve the same behaviour. I have made a specific question for this : stackoverflow.com/questions/28024425/…
-
Archaeopterasa over 9 years@pe60t0 The auto-added constraints can be removed (the ones that show up in debug output as _UILayoutSupportConstraint instead of NSLayoutConstraint). In my code I wanted to constrain a child view controller's layout guide to match the parent view controller's, so I first went through the constraints list and removed the one that set its height, and then was able to constrain it like a normal view. (The catch, since there always is one in autolayout: orientation changes are potentially messy, and require careful order-of-operations.)
-
pronebird over 9 yearsYou don't call super, this is very neat LOL
-
Loadex about 9 yearsThis works perfectly in 99% cases. But not if you have a UINavigationController as a childViewController.
-
Conor about 9 yearsThank you. I had to add a
[_tableView setContentOffset:CGPointMake(0, 0 - _tableView.contentInset.top) animated:NO];
to have it scroll to the top correctly -
Tim Johnsen over 8 yearsThis just saved me a huge headache :)
-
Majid Bashir over 7 yearsThis solution worked for me in case for page controller , if someone has a page controller and added constraints to the view for child
-
LOP_Luke over 7 yearsThis answer fixed the top layout guide issues in my child view controllers with no navigation bar. Swift 3 version:
override func viewWillLayoutSubviews() {self.topGuideConstraint.constant = (self.parent ?? self).topLayoutGuide.length}
-
Gligor almost 7 yearsWhat a genious! Thank you very much for this answer, saved me from pulling my hair out.
-
Jonathan. about 6 yearsCan you expand on this answer? I can't work out what constraint to add. Everyone that I add conflicts with an existing constraint on iOS 10.
-
koen over 5 yearsUnfortunately this no longer works in iOS 11 and higher:
'topLayoutGuide' is deprecated: first deprecated in iOS 11.0 - Use view.safeAreaLayoutGuide.topAnchor instead of topLayoutGuide.bottomAnchor