I want to use the Top Layout Guide
in the UIScrollView
through Auto Layout. Without the UIScrollView
Auto Layout works well with Top Layout Guide
.
But when I embed the UIButton
in UIScrollView
, it doesn't.
I know that is because UIScrollView
is not the same hierarchy level with Top Layout Guide
. But I think there may be a good solution to resolve this issue.
You are right to be confused. It is a bit counterintuitive but the top and bottom layout guides are irrelevant to configuring a UIScrollView so that its scrollable content will underlap the translucent navigation bar, which is the effect you are trying to achieve.
Given the view hierarchy you've shown in the second picture, this is what you need to do on iOS8:
Configure the view controller so that "Extend Edges Under Top Bar" is checked (in code, use edgesForExtendedLayout
). This will ensure that the view controller's lays out its root view so that it underlaps the nav bar.
Configure the scroll view constraints so that the scroll view's top edge has a zero offset from the top edge of its superview, not zero space from the top layout guide. This will ensure that the collection view fills the root view and thus also underlaps the nav bar, which is necessary for the scroll view's content to be able to scroll under the nav bar. (IB might fight you on this. See the footnote below.)
So now how do you make sure that the scroll view has any idea where the nav bar is, so that (for instance) it doesnt't always position its content under the nav bar? The answer has nothing to do with layout guides. In the view controller, check the box "Adjusts Scroll View Insets" (or in code, automaticallyAdjustsScrollViewInsets
). This will cause the view controller to automatically adjust the scroll view's contentInset
property so that the scroll view positions its content appropriately.
This will work.
So why is this the answer? And why is it so confusing?
Frankly, it's easy to get confused because the top and bottom layout guides are prominently presented to us as elements that convey layout information about translucent overlaid elements. However, they are not the only "translucency-aware" layout mechanism. They are directly relevant only for positioning of "normal" subviews, i.e., not the view controller's root view, and not content within a UIScrollView.
Content within a scroll view (or a subclass like UICollectionView and UITableView) will always be positioned in a more complicated way involving the scroll the view itself, affected by properties like contentInset
, contentOffset
, etc.. (Really, if scroll view layout were a straightforward thing, why would Apple have dedicated WWDC sessions to scroll view layout for the last four years running?!)
To summarize, as the steps above indicate, the three distinct translucency-aware mechanisms for managing layout are as follows:
Extends Edges determines if the view controller positions its root view so that it underlaps the nav bar.
Layout Guides provide a metric that tells where the "main" content area is, taking translucent bars into account. You can use these with Auto Layout to position normal views so they don't underlap. Or you can access the numerical values in code.
Scroll View Insets are the right way to ensure that a scroll view's content can underlap but doesn't always underlap. The automaticallyAdjustsScrollViewInsets
property on the view controller can do this for you automatically in simple cases. (Presumably, this property just causes the view controller to update the scroll view's contentInset
based on the same values it exposes via the layout guides. So if you needed to manage the insets yourself, that's how you would do it.)
A footnote on "Fighting with IB":
Unfortunately, Interface Builder might fight you when you try to constrain the scroll view edge to its superview's edge. If you do a ctrl-drag from the scroll view to the superview, when it pops up the menu of possible constraints to add between those views, it might try to get you to constrain the scroll view against the view controller's layout guides. This is because IB mindlessly prefers layout guides to superview edges, when the superview is the root view. But when you're working with a scrollview, this is the wrong thing.
Why? For instance, suppose you accept a constraint against the layout guide. Then you will end up with a top constraint on your scrollview that constrains it to topLayoutGuide-64.0. That -64.0 is a hard-coded value compensating for the exact height of a nav bar. So what happens when one fine day the nav bar does not equal 64pt? Or when you simply turn off the nav bar entirely? Or want to re-use this scene in a context without a nav bar? Answer: then you get a broken layout.
So how do you force IB to add a constraint from the scroll view to its superview's edge, as opposed to the layout guide? As far as I can tell, the answer is that you can't add that constraint correctly in IB by doing a ctrl-drag between views.
Instead, you need to select the view, and then use the "Pin" control at the bottom of the canvas. This is the one that looks like a capital H with a box in its middle. In the top section of the Pin popup dialog, the section with the little diagram showing superview space constraints, you can use the dropdown controls next to the text fields to configure if the space constraint binds a layout guide or a superview. This is shown below:
Github link to demo projects: https://github.com/algal/ScrollViewUnderlapDemo
While algal's answer seems to have worked prior to iOS 9.0 it is unnecessarily complicated and broken beyond iOS 9.0. The easier way that also works beyond iOS 9.0 and requires no interaction with auto layout is to simply do the following:
Adjust Scroll View Insets
is checked for the ViewController in Interface Builder (or set automaticallyAdjustsScrollViewInsets
to true
programmatically).View
to UIScrollView
in Interface Builder (or replace it (self.view) manually with an UIScrollView in code).If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With