INTERFACE BUILDER AND UISCROLLVIEW — X — FILE

Andrea Finollo
8 min readJan 4, 2021

--

It has been a long since the last time I had to setup a UIScrollView in interface builder, I usually do programmatically and a lot has changed so here is a little post about how to configure a scroll view entirely using interface builder.

Reading this article you will understand:

  • how to setup a UIScrollView without showing errors in interface builder
  • differences between frame layout guide and content layout guide
  • definition and importance of the intrinsic content size
  • create a sticky header just by using interface builder

Level:

  • Beginner

Required:

  • basic knowledge on how to create constraints in interface builder
  • basic knowledge on how to create an app

SET UP THE PROJECT

Let’s make a UIScrollView inside a xib or a storyboard under this specifications:

  • Scroll view must scroll only vertically
  • The width of the content must be the same as the width of the scroll view (also to avoid horizontal scroll)
  • The content can change dynamically in its height and the scroll content must fit accordingly

Basically this is what happens when you need to get information from some web services and you don’t know the length of the content, could be a simple text or a more complex UI.

Begin by creating a simple application with one view.

Add a UIScrollView as a subview of UIViewController view and set its constraints to pinned to its superview

UIScrollView creation
Setting up the scroll view

Then we will need a UIView as a subview of the scroll view, this will be our content view where we are going to put all our contents

Why a content view?

It could be any kind of view actually, but I usually do that because it helps in understanding which is the content I would like to scroll, it also helps me to understand better the resulting intrinsic content size.

INTRINSIC CONTENT SIZE

Why intrinsic content size is important and what is it in the first place?

The intrinsicContentSize is a property of a UIView, in the documentation Apple defines it as:

The natural size for the receiving view, considering only properties of the view itself.

Read it again: considering only properties of the view itself.

The instrinsic content size represents the size occupied by the content of a view. For a UILabel it is the space occupied by a text, for a UIImageView it is the size of the contained image. This is valid unless you add some constraints to them that makes those views shrink or expand.

But what is the instrinsic content size of a plain UIView? it is simply the sum of all the contained subviews intrinsic content sizes if constrained to their superview and between them properly.

SETUP THE SCROLL VIEW

So, if you want to do not set the height and/or width of the scrollable area but you want it to be configured on the actual content this concept becomes super important. I mean, don’t you find pretty ugly scroll views that make you scroll a lot but the content is just a tiny part just because their size it is hard coded?

I do!

What we must do is a scroll view that scrolls content on y-axis and that is fixed on x-axis. In another way we want the scrollview be able to show the content in its length, also the scrollview must stop to scroll when it reaches the end of the content.

After you add scrollview constraints you will see that interface builder still complains a lot about missing constraints and it does because now it is missing content in the scroll view.

Interface builder errors due to the fact that there is no content inside the scroll view

Let’s add our content view e set its constraints. First let us add the constraints that will fix the content view in respect of the scroll view content. Light yellow is the scroll view, dark yellow is the content view.

With the content view selected keep the CONTROL key pressed, drag a line over the content layout guide and select the first four results, after that change their constant values to 0. We are saying that we don’t want any padding between the scroll view content area and the content itself.

Creating content view constraints

The content layout guide represent content area of the scroll view.

Now you will still see some errors in interface builder and this happens becuse the scroll view still doesn’t know the size of the content in it.

To fix that can do plenty of things, the first one will be set a fixed height and width to our scroll view, but our specification were:

  • Scroll view must scroll only vertical
  • The width of the content must be same as the scroll view
  • The vertical content must change dynamically and the scroll content must fit accordingly

So we select the content view, press CONTROL and drag a line over the frame layout guide and we set the width to be equal, make sure to modify the multiplier to 1.

Fixing content view width to be equal to scrollview width

Now we set the height of the content view to be fixed to a value.

Adding fixed height to the scroll view

Didn’t you just told us that it must be dynamic and based on the content view intrinsic content size?

Yes I remember that, but since our content view contains nothing and the intrinsic content size is impossible to be calculated, to get rid of the annoying error in interface builder we must give it a meaningful size.

Now the magic part, we select the height constraint of the content view just created and set it to be removed at runtime. This means that the constraint exists only in interface builder, but not when we launch the application.

Removing height constraint at runtime

Pfiu, all errors are gone.

Let’s set out real content:

  • Get a UIStackView
  • Add it as a subview of our content view
  • Set its constraints to fill the content view space
  • Go into out view controller class and create outlets for: scrollview, content view and stack view

What will be the intrinsic content size of our stack view at runtime? the sum of its subviews intrinsic content sizes, since it doesn’t have any it is 0 in height.

LET’S CODE

To give contents to the stack view I’ve created a method that based on a random number creates UILabel instances with a random string and add them to the stack view.

These are some handy extensions to create random background colors and random text.

extension String { 
static func random(length: UInt = 20) -> String {
let base = “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789”
var randomString: String = “”
for _ in 0..<length {
let range: Range<UInt> = 0 ..< UInt(base.count)
let randomValue = UInt.random(in: range)
randomString += “\(base[base.index(base.startIndex, offsetBy: Int(randomValue))])”
}
return randomString
}
}
extension UIColor {
static var random: UIColor {
return UIColor(red: .random(in: 0…1),
green: .random(in: 0…1),
blue: .random(in: 0…1),
alpha: 1)
}
}

This is the code in the UIViewController

override func viewDidLoad() {
super.viewDidLoad()
fillStackView()
}
private func fillStackView() {
for index in 0 ..< Int.random(in: 15 … 90) {
let label = createLabel()
stackView.insertArrangedSubview(label, at: index)
}
}
private func createLabel() -> UILabel {
let label = UILabel()
label.numberOfLines = 0
label.text = String.random(length: UInt.random(in: 20 … 100))
label.backgroundColor = .random
return label
}

Each time you will lauch the application you will see that the scrollable content changes based on the actual content of the stack view or the sum of the height of the labels. The height of the labels is just their instrinsic content size, the space occupied by the text (plus some padding).

Each color represent a single UILabel

Ok, but what if I change the top layout content constraint to a value bigger than 0? you will simply set the content view to start with a space between the scroll view top and the content view top. Note: Is not the same thing as to set a content inset. Content inset is mainly used for overlapping bars, for instance when you use a UINavigationController with a UITableViewController or a UICollectionViewController

STICKY HEADER

Back then, one of the most cool UI features were pinned headers inside a scrollview and I remeber it was not that simple to achieve that in interface builder. Using the new scroll view layout guide is super simple, let me show you.

Select the stack view top constraint and change its constant to 50.

Adding a padding between the content view(dark yellow)and the stack view(red)

Now take a UIView and place it in the space between the content view and the stack view as a subview of the content view.

Pin the its top constraint to the content view top with a 0 constant, do the same for left and right (20) and set its height to 35.

Selecting the header view press CONTROL key and drag a line to the scroll view frame layout guide and select the top layout, set its constant to 0.

Setting up the sticky header

Now we are in a situation that could lead to conflicting constraints (and we want to 😎)because we are telling the header view that the distance between the header and the content view must be zero (D1) and the same for the distance between the header and the scrollview frame (D2). While we scroll we are going to break the first condition, this can be simply fixed by modified by changing priorities of the constraints and their relation.

Select the constraint between the top of the header and the top of the content view and lower its priority to 999 instead of 1000.

Select the constraint between the top of the header and the top of the scroll view frame layout guide and set its relation to be bigger or equal to 0.

This translates into this behaviour, when I scroll down the header is attached to the content view while D2 can increase freely because we set its constraint to be e equal or bigger than 0. If we scroll up the header will be sticked to the scroll view and content will go under while scrolling. This is possible because D1 want to increase but D2 want to keep 0, they are conflicting, since we modified the priorities the one with the highest priority will win over the other one and in this case D2 wins.

Scrolling down: the sticky header (purple) stays in place
Scrolling up: the stickyheader stays pinned to the top and the content scrolls under it
Header at zero offset

RECAP

So far you’ve seen:

  • Ho to create a UIScrollView in Interface builder
  • The ludicrous importance of the intrinsic content size
  • The separation between the content layout guide (the area where you will put your content) and the frame layout guide
  • How to create a simple sticky header by playing around with constraints

SOURCES

Apple documentation

XCode 12

--

--

Andrea Finollo
Andrea Finollo

No responses yet