5 Scroll View¶
By now, you understand the power of stack views. But what options do you have when you need to create user interfaces that go beyond the screen size? Scroll views.
Scroll views allow you to expand your interfaces beyond the limits of the screen. As written in the official Apple documentation at https://apple.co/2WoN2Be:
UIScrollView: A view that allows the scrolling and zooming of its contained views.
A scroll view acts as a parent view and can contain as many views as your interface needs; they are a key component for any app that requires more space. For example, you might need more space to display parts of a form or when dealing with a significant amount of text. Or maybe your app includes a large image that users need to zoom into and some way to navigate the UI that mimics a carousel. These are all great reasons to use a scroll view, and in this chapter, you’ll learn how to use them in your app.
Working with scroll view and Auto Layout¶
Scroll views are different than other views when it comes to Auto Layout because you need to make two types of constraints: One that sets the x, y, width and height of the scroll view, and one that sets the x, y, width and height of the subviews in the content area. Generally, when you make constraints between the scroll view and views outside of its view hierarchy, you set the scroll view’s frame. But, when you make constraints between the scroll view and views inside of its view hierarchy, you set the frame of the subviews in the scrollable content area.
When working with scroll views, keep the following in mind:
- You need to define both the size and position of the scroll view’s frame within its superview and the size of the scroll view’s content area.
- To define the content area, it’s best to add a content view that’s anchored to the edges of the scroll view.
- If you don’t want to have horizontal scrolling, make the content view’s width equal to the scroll view’s width. The same is true for vertical scrolling but using the height instead.
- When adding content, add constraints related to the content view. If you want to create a floating effect for a view inside of the scroll view, add constraints between the target view and objects outside of the scroll view.
Adding the Options Menu to the Profile Screen¶
Go to the starter project, open the MessagingApp project, and build and run.
The app already contains part of the UI you need; however, the bottom part is missing. You’ll need to add some buttons so that the user can see all of the options.
Go to ProfileViewController.swift and add the following code below viewDidAppear(_:)
:
private func setupMainStackView() {
mainStackView.axis = .vertical
mainStackView.distribution = .equalSpacing
mainStackView.translatesAutoresizingMaskIntoConstraints =
false
view.addSubview(mainStackView)
let contentLayoutGuide = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
mainStackView.leadingAnchor.constraint(equalTo:
contentLayoutGuide.leadingAnchor),
mainStackView.trailingAnchor.constraint(equalTo:
contentLayoutGuide.trailingAnchor),
mainStackView.topAnchor.constraint(equalTo:
contentLayoutGuide.topAnchor),
])
setupProfileHeaderView()
setupButtons()
}
With this new method, you:
- Set up
mainStackView
, indicating its alignment, axis and distribution. You also set itstranslatesAutoresizingMaskIntoConstraints
tofalse
. ThemainStackView
is already declared at the top of the class. - Add
mainStackView
toview
so that you can add it to the view hierarchy. - Create the constant
contentLayoutGuide
to get a reference to theview.safeAreaLayoutGuide
. You’ll create constraints related to this layout guide instead of the view itself. In this case, you’ll use thesafeAreaLayoutGuide
, which allows creating UIs respecting the margins on the devices, so things like the iPhone X notch do not interfere with the views. - Set the leading, trailing and top constraints for
mainStackView
so that you can properly position it on the screen. Since the Stack View will grow vertically, it’s not necessary to indicate the bottom constraint.
Go to setupProfileHeaderView()
and replace its code with the following:
profileHeaderView.translatesAutoresizingMaskIntoConstraints =
false
profileHeaderView.heightAnchor.constraint(
equalToConstant: 360).isActive = true
mainStackView.addArrangedSubview(profileHeaderView)
This code:
- Removes all of the previous constraints and adds one constraint for the height.
- Adds
profileHeaderView
tomainStackView
.
Because the view is now inside of a stack view, you don’t have to add the same constraints you added before; the alignment and distribution properties will cause the contained views to resize accordingly. That’s part of the magic behind working with stack views.
Go to viewDidLoad()
and replace the call to setupProfileHeaderView()
with the following:
setupMainStackView()
Build and run.
The app now displays some options in the bottom section; however, there are additional options you can’t see. More importantly, if you rotate the simulator using Command-Right-Arrow, you’ll see even fewer options or not at all depending on the device:
Stop the project, and in ProfileViewController.swift, go to setupButtons()
. Look for the following block of code:
func setupButtons() {
let buttonTitles = [
"Share Profile", "Favorites Messages", "Saved Messages",
"Bookmarks", "History", "Notifications", "Find Friends",
"Security", "Help", "Logout"]
let buttonStack = UIStackView()
buttonStack.translatesAutoresizingMaskIntoConstraints = false
buttonStack.alignment = .fill
buttonStack.axis = .vertical
buttonStack.distribution = .equalSpacing
buttonTitles.forEach { (buttonTitle) in
buttonStack.addArrangedSubview(
createButton(text: buttonTitle))
}
mainStackView.addArrangedSubview(buttonStack)
NSLayoutConstraint.activate([
buttonStack.widthAnchor.constraint(equalTo:
mainStackView.widthAnchor),
buttonStack.centerXAnchor.constraint(equalTo:
mainStackView.centerXAnchor)
])
}
This code takes an array named buttonTitles
and creates a button for each title. In this case, there are ten titles, but the app doesn’t show all of them because the size of the stack view after — adding all the buttons — is greater than the space available on the screen. To fix this problem, you’ll use a scroll view.
Setting up the scroll view¶
Go to the top of ProfileViewController
and add a new property after the mainStackView
declaration. This new property will contain the Scroll View:
private let scrollView = UIScrollView()
Next, add this method below setupMainStackView()
:
private func setupScrollView() {
//1
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
//2
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo:
view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo:
view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo:
view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo:
view.safeAreaLayoutGuide.bottomAnchor)
])
}
With this new method, you:
- Set
translatesAutoresizingMaskIntoConstraints
tofalse
so thatscrollView
won’t translate the autoresizing mask into constraints. Because you’re creating all of the constraints using Auto Layout, this translation is not necessary. - Create leading, trailing, top and bottom constraints for
scrollView
anchored to its superview. Since these are constraints to a view outside of the scroll view’s hierarchy, they’ll set the scroll view’s frame.
You now need to put mainStackView
inside of the newly created scroll view.
Replace setupMainStackView()
with the following:
private func setupMainStackView() {
mainStackView.axis = .vertical
mainStackView.distribution = .equalSpacing
mainStackView.translatesAutoresizingMaskIntoConstraints
= false
//1
scrollView.addSubview(mainStackView)
//2
let contentLayoutGuide = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
//3
mainStackView.widthAnchor.constraint(equalTo:
view.widthAnchor),
mainStackView.leadingAnchor.constraint(equalTo:
contentLayoutGuide.leadingAnchor),
mainStackView.trailingAnchor.constraint(equalTo:
contentLayoutGuide.trailingAnchor),
mainStackView.topAnchor.constraint(equalTo:
contentLayoutGuide.topAnchor),
//4
mainStackView.bottomAnchor.constraint(equalTo:
contentLayoutGuide.bottomAnchor)
])
//5
setupProfileHeaderView()
setupButtons()
}
This code looks similar to the previous implementation; however, they are some important modifications:
- You made
mainStackView
a subview ofscrollView
. - The constant
contentLayoutGuide
now contains a reference to the scroll view’s Content Layout Guide. This layout guide represents the content area of thescrollView
. You could have used thescrollView
itself to create the constraint, and it would look the same, but doing it as you did here brings more clarity to your code. - There’s a new constraint for the width of the
mainStackView
. Notice thatwidthAnchor
is created in relation to the view. That’s because a scroll view takes on the size of its content, and not doing it this way will cause the scroll view to shrink. Also, by doing this, you’re indicating that horizontal scrolling is disabled. All the other constraints are betweenmainStackView
and thecontentLayoutGuide
reference created above. - There’s another new constraint between the bottom of the
mainStackView
and thecontentLayoutGuide
. This is what makes thescrollView
grow so that it can fit all of the views.
The only thing left to do is to call your set up methods. In viewDidLoad()
, add the following immediately before the call to setupMainStackView()
:
setupScrollView()
Build and run.
Using the Frame Layout Guide¶
Before you move on, there’s one more refactor you can do. Go to setupScrollView()
and replace the implementation with this one:
private func setupScrollView() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
//1
let frameLayoutGuide = scrollView.frameLayoutGuide
//2
NSLayoutConstraint.activate([
frameLayoutGuide.leadingAnchor.constraint(equalTo:
view.leadingAnchor),
frameLayoutGuide.trailingAnchor.constraint(equalTo:
view.trailingAnchor),
frameLayoutGuide.topAnchor.constraint(equalTo:
view.safeAreaLayoutGuide.topAnchor),
frameLayoutGuide.bottomAnchor.constraint(equalTo:
view.safeAreaLayoutGuide.bottomAnchor)
])
}
Here’s what you did:
- Create the constant
frameLayoutGuide
to get a reference to thescrollView.frameLayoutGuide
. You’ll create constraints related to this layout guide instead of the scroll view itself. This layout guide refers to the frame of the scroll view, not the content area. - Create the leading, trailing, top and bottom constraints between the Frame Layout Guide of
scrollView
and the view. Notice how the leading and trailing constraints are created using the view, not the Safe Area Layout Guide; in this case,scrollView
will occupy the entire width of the screen.
Build and run.
It looks like nothing changed, but with the use of the Frame Layout Guide, your code is now more precise and easier to understand. If you want to learn more about Layout Guide, read Chapter 7, “Layout Guide.”
That wasn’t too bad, was it? With the new options menu you added, users will have a better experience with your app. Before wrapping things up, try running the app on different simulators so you can see how it works on any screen size.
Challenge¶
For this challenge, you’ll need to create the Settings button and add it to the scroll view. This button should appear near the bottom of the scroll view and close to the right side, but it should not be part of the stack view. For reference, here’s how it should look:
Key points¶
- Constraints between a scroll view and the views outside of its view hierarchy act on the scroll view’s frame.
- Constraints between a scroll view and views inside of its view hierarchy act on the scroll view’s content area.
- Be extra careful when setting the size and position of your scroll view. Remember that apart from setting its size and position, you’ll have to specify a content area that will affect the way the scroll view behaves.
- It’s strongly recommended to add a content view that acts as a container for all of the views inside of the scroll view. This makes it easier to work with a scroll view that can grow in size.
- While working with stack views (or any views) inside of a scroll view that acts as the content area, the width and height determine if the scroll view will have vertical and horizontal scrolling.
- Remember to use
frameLayoutGuide
andcontentLayoutGuide
when creating the constraints for scroll views.