Configuring a UIScrollView in a Storyboard — with no code!

A delicious developer recipe for setting constraints on Storyboard views to serve up a proper scrolling view for iOS applications.

Introduction

Configuring a UIViewController with a scrolling content view can be confusing, and — frustratingly — scrolling views usually don’t really work at all until all their constraints are perfect!

To understand how to setup a scrolling UIView within a UIScrollView, first we need to understand the views that will be involved, and how they relate to each other. Once we understand this, the concept of scrolling views becomes much simpler to understand and configure.

The Lay of the Land

First we need to understand the relationship between the views involved in the scrolling. Review the following diagram, which I’ll discuss just below.

Starting from the top of the view stack, these are the views we’ll be configuring:

Content

At the top of this diagram — represented by the white boxes — are a set of controls that the user will see and interact with. There can be as many as you like, and they should be arranged as they make sense for the UI.

Presumably, the height will exceed the vertical size of the enclosing UIView — if it didn’t, we wouldn’t need to scroll the content, right?

Content View (UIView)

The Content we want to present to the user is then arranged within a Content View. This doesn’t have to be a UIView specifically, but should be something based on UIView. In the implementation walk-through below, I’ll actually use a UIStackView as the Content View, and just stack from UIViews within it until the vertical space exceeds the size of my iPhone’s screen.

This technique isn’t limited to view hierarchies. Your content view could be a UIView that you draw content in yourself. Anything goes, really.

Scroll View (UIScrollView)

The Scroll View is the container that knows show to pan the Content View around so the user sees as much of the Content View as will fit in the Scroll View’s visible frame. The rest of the Content View is clipped off the top or bottom (or right/left, if the width exceeds the frame.width). Yes, I just defined scrolling. And managing the visible and clipped regions is all the UIScrollView does in this solution.

Users don’t typically see content placed on the Scroll View —al though if it has a background color, they may see that color when the Content View is smaller than the Scroll View frame.

Top Level View (UIView)

The Scroll View has to live somewhere, and that somewhere is usually pinned to the edges of some containing view. When adding a Scroll View to an iPhone app, this will be the top-level UIView of a UIViewController. However, it could be any subview instead— for example in an iPad app, the Scroll View could be pinned to the edges of a Split View’s content view.

In this example, I’ll stick to the simple case of a scrolling view placed in an iPhone application’s main view frame.

Constraints

Making the Scroll View work correctly is almost entirely dependent on getting the constraints created correctly in Xcode’s Interface Builder. By correctly, I mean connecting the edges of the controls appropriately and getting the size of the content view set correctly.

For the majority of iPhone applications, where a content view is scrolled vertically, the following simple checklist of constraints will work 90% of the time. This recipe requires creating one set of constraints on the Scroll View, and a second set of constraints on the Content View.

For the following constraints, I’m using the view names [Scroll View], [Content View] and [Top Level View]. Refer to the above diagram to recall the arrangement of these views.

Scroll View Constraints

The following constraints go on the Scroll View. Keep in mind that the [Safe Area] in the following constraints refer to the Superview of Scroll View, which is Top Level View.

  1. [Scroll View].[Trailing Space] = [Safe Area]
  2. [Scroll View].[Leading Space] = [Safe Area]
  3. [Scroll View].[Bottom Space] = [Safe Area]
  4. [Scroll View].[Top Space] = [Safe Area]

OK, these constraints are super simple! Basically, just pin the UIScrollView to the containing view. These don’t have to be precisely what I’ve listed.

I pinned to the Safe Area on an iPhone X here. If you have some other views on the same form (e.g. some navigation buttons), you might pin a Scroll View edge to those sibling views. Or, if you don’t want to stay within the Safe Area, you can pin to the edge of the containing view instead.

Position the Scroll View where your design suggests it should be — the point is that you want the scroll view size to be fixed in place relative to other elements on the screen.

Content View Constraints

Now that the Scroll View is set to a fixed position, we’ll setup the constraints for the content view.

Since the Content View is contained within the Scroll View, the Superview in the constraints below refers to the Scroll View (i.e. not the Top View).

  1. [Content View].[Trailing Space] = [Superview]
  2. [Content View].[Leading Space] = [Superview]
  3. [Content View].[Top Space] = [Superview]
  4. [Content View].[Bottom Space] = [Superview]
  5. Equal Width: [Content View] & [Top Level View]
  6. (maybe) Height: 1500

The first four of these constraints are super-simple: the Content View edges are pinned to the Scroll View edges. This is exactly what we did with the Scroll View — we pinned it to the Top Level View.

What’s less intuitive is that — at the same time — we have a width and height constraint applied to the Content View. Huh?

These last two constraints allow our content view to have a virtual size that exceeds the visible frame of the Scroll View.

In this case, I’m designing an iPhone app, and I want vertical scrolling, but not horizontal scrolling. To achieve this, I’ve set the Content View horizontal size equal to the Top View size. Because of this, the Scroll View will never perceive a need to scroll the content horizontally. The user won’t be able to scroll horizontally, and the horizontal scroll bar won’t be presented. This is a common technique for iPhone apps, which rarely scroll horizontally when in portrait orientation.

I do want vertical scrolling. There are essentially two ways to ensure the vertical height is known when Scroll View decides whether to present a scroll bar to the user:

  1. By setting a constraint such as #6 to set that hight specifically. You may need to do this when, for example, you’ll be drawing content at runtime in Swift code. You can set the height of this view at runtime by creating an outlet to this constraint, or changing the Content View frame size in code at runtime.
  2. By using a Content View that has some intrinsic size. For example, if you use a UIStackView for the Content View, and the UIStackView has a vertical size known and design-time, then there’s no need to worry about setting a Content View height constraint at all — it will be inferred by the content of the Stack View.

A common mistake is to pin the Content View to the vertical size of the UIScrollView. If you do this, the content won’t scroll — even when there is actual content “off screen”.

Using The Recipe

Now that we have a recipe — in terms of a view hierarchy and a checklist of constraints, let’s put it into practice in a demo application (which you can download from GitHub using the link at the end of this article).

To keep this article shorter, I’ve summarized the steps, since I assume you know how to use Xcode to create apps and views already:

  1. Create a new Single View App
  2. In the default Main.Storyboard, select the View Controller Scene, then select the Size Inspector, then change the View Controller Scene’s Simulated size to Freeform, and the Height property to 1500
  3. create seven views, one over the other, assigning the following colors of the rainbow: #9400D3, #4B0082, #0000FF, #00FF00, #FFFF00, #FF7F00, #FF0000
  4. Add a height constraint to each view, fixing each to 200 points
  5. Highlight all seven views in the Document Outline, and select from the Xcode menu: Editor / Embed In / Stack View.
  6. Highlight he new UIStackView in the Document Outline, and in the Attributes Inspector, set the following properties on the UIStackView:
    a. Axis = Vertical
    b. Distribution = Equal Spacing
    c. Spacing = 10
  7. Highlight the new UIStackView in the Document Outline, and select from the Xcode menu: Editor / Embed In / Scroll View
  8. Highlight the new Scroll View in the Document Outline, and create constraints 1–4 from the above Scroll View Constraints section list.
  9. Now in the Document Outline, click on the UIStackView. Now hold down the ⌘ key and click on the Top Level View. With both these views highlighted, click on the Add New Constraints button, select the Equal Widths checkbox, and press the Add Constraints button to save this constraint.

You don’t need constraint #6 because the Content View in this layout is a UIStackView that has an intrinsic height, since we fixed the height of all the rainbow UIView controls and set a spacing of 10 points. This gives the UIStackView a fixed height of (7 * 200) + (6 * 10) = 1460, which the UIScrollView will read at runtime to use to position and scroll the view.

Guess what? You’re done!

Your View Controller in the Storyboard should look similar to the following. Note that in the Document Outline I set the Xcode Specific Label property for each view to help you read through my outline easier. Your version may not have labels such as “Violet, Indigo”.

View Layout for the Scrollable Rainbow UIStackView

Now add the following Swift code. No — just kidding! No code. This scrolling solution is complete with no code at all. Yay!

Run the application, and scroll the rainbow views in the Scroll View. Your application should look like the following. Note: if you’re using the Medium app, this image may be blank. If so, open this page in a web browser to see this animated GIF.

Download the Code

You can download the code for this tutorial here:

https://github.com/robkerr/TutorialScrollingView

Leave a Reply

Your email address will not be published. Required fields are marked *