Laying out a scroll view is a confusing task. I found it easier when, in iOS 11, Apple introduced frame and content layout guides. Too bad they neglected to add them to Interface Builder. That changed in Xcode 11. Here’s a quick guide on how to use them.
Why Are Scroll View Layouts So Hard?
For a recap on why scroll views are confusing and how the frame and content layout guides help see this earlier post on easier scrolling with layout guides. Here’s the layout I built in code using layout guides:
You need two groups of constraints to layout a scroll view:
- constraints that fix the frame (size and position) of the scroll view relative to its superview.
- constraints that layout the content relative to the content area of the scroll view and constrain its size.
Building my layout with Interface Builder and Xcode 10 I might end up with constraints like this:
- Four constraints pin the scroll view to the edges of the root view, fixing its frame.
- Four constraints between the stack view and the scroll view pin the content to the scroll view content area.
- The width of the stack view is fixed to the width of the scroll view frame.
I never found this to be very intuitive:
- Constraints between the scroll view and a super view act on the frame of the scroll view.
- Constraints between the scroll view and a subview act on the content area of the scroll view.
- Height or width constraints between the scroll view and a subview use the frame of the scroll view.
The introduction of the frame and content layout guides in iOS 11 promised to make things clearer. Unfortunately, Apple neglected to add support for them in Interface Builder.
What Changed In Xcode 11
From the Xcode 11 release notes:
Content and Frame Layout guides are supported for UIScrollView and can be enabled in the Size inspector for more control over your scrollable content. (29711618)
The content and frame layout guides are enabled by default in Xcode 11 when you drag a new scroll view into the canvas:
You can enable them for an older layout using the size inspector:
The best way to explain how to use them is with an example.
Using The New Layout Guides In Interface Builder
Let’s build the previous layout in Interface Builder using the content layout guides:
To get started I have arranged the content for my layout in a vertical stack view that I have embedded in a scroll view in Interface Builder:
Let’s fix the size and position of the scroll view by pinning it to the edges of the root view. I find that easier to do if you first hide the safe area layout guide for the root view (using the size inspector):
Select the scroll view and then use the “Add New Constraints” tool to add the four constraints between the scroll view and the edges of the root view:
Check the constraints in the navigator to make sure you’re not using the safe area or accidentally adding padding to any of the constraints:
We want to pin the stack view to the edges of the content area. Control-drag from the stack view to the Content Layout Guide:
Then add the four constraints to pin each edge of the stack view to the guide:
Check the constraints in the navigator:
I don’t want the layout to scroll horizontally so I also need to fix the width of the content area to match the width of the scroll view frame. It’s not possible to create an equal width constraint between the content and frame layout guides in Interface Builder. Instead, control-drag from the stack view to the frame layout guide:
Then add an equal widths constraint:
The final set of nine constraints using both the content and frame layout guides:
What’s Still Missing?
I don’t want to complain. It’s good to see the content layout guides show up in Interface Builder. It’s been two years since they were introduced with iOS 11 but better late than never. To complete the picture I’d like to see Apple also expose the layout margin guide for the scroll view.
Sometimes you want a view that remains fixed in the scroll view frame so that it floats over the scrollable content. For example, the info button in my previous example remains fixed in the top-left corner when the content scrolls:
The frame layout guide doesn’t help in this case as it extends into the safe area behind the navigation bar. I created constraints in code to fix the button to the layout margins guide of the scroll view:
infoButton.leadingAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.leadingAnchor), infoButton.topAnchor.constraint(equalTo: scrollView.layoutMarginsGuide.topAnchor)
If you try to do that in Interface Builder the constraints are assumed to be with the content area so the button scrolls with the content. The closest you can get is to use the safe area layout guide of the scroll view (enable it in the size inspector) with some extra padding: