Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with Top Layout Guide in UIScrollView through Auto Layout

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. enter image description here

But when I embed the UIButton in UIScrollView, it doesn't. enter image description here

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.

like image 978
Jonghwan Hyeon Avatar asked May 07 '14 04:05

Jonghwan Hyeon


2 Answers

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.

what to do

Given the view hierarchy you've shown in the second picture, this is what you need to do on iOS8:

  1. 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.

  2. 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.)

  3. 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.

what's going on

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:

  1. Extends Edges determines if the view controller positions its root view so that it underlaps the nav bar.

  2. 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.

  3. 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.)

fighting IB's layout guide mania

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:

Pin dialog, for constraining layout guides vs view

Github link to demo projects: https://github.com/algal/ScrollViewUnderlapDemo

like image 58
algal Avatar answered Nov 17 '22 00:11

algal


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:

  1. Ensure Adjust Scroll View Insets is checked for the ViewController in Interface Builder (or set automaticallyAdjustsScrollViewInsets to true programmatically).
  2. Set the class of the ViewController's root View to UIScrollView in Interface Builder (or replace it (self.view) manually with an UIScrollView in code).
like image 33
Daniel Schlaug Avatar answered Nov 16 '22 22:11

Daniel Schlaug