How to use ScrollView in SwiftUI

⋅ 6 min read ⋅ SwiftUI List Navigation ScrollView

Table of Contents

Part 2 in the series "Building Lists and Navigation in SwiftUI". We will explore a ScrollView, UIScrollView equivalent in SwiftUI.

  1. Content View
  2. ScrollView
  3. ListView
  4. NavigationView

List view is a view that contains child views and displays them in a scrollable manner. SwiftUI offers two views with this capability, ScrollView and List.

In the previous post, we learned two ways to populate a list view's content. We put our content in something called view builder. @ViewBuilder will pack all child views into a tuple for a parent to work with. The default behavior of View will simply layout them vertically, which is not scrollable. In this article, we will visit one of a list view, ScrollView.

What is ScrollView

ScrollView displays its content within the scrollable content region. It is a UIKit's UIScrollView equivalent in SwiftUI.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Creation

If you look into an initializer signature, you will see that it accepts a content parameter that is annotated as a view builder. So you can populate it the same way we did in the last post.

public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, @ViewBuilder content: () -> Content)

Static Content

To create a static content scroll view, we create a scroll view passing child views as a content argument.

struct ContentView: View {
var body: some View {
ScrollView {
Text("First")
Text("Second")
Text("Third")
Text("Fourth")
Text("Fifth")
Text("Sixth")
Text("Seventh")
Text("Eighth")
Text("Ninth")
Text("Tenth")
}.font(.largeTitle)
}
}

By default, scroll view size will equal the space of its largest child needed. If you want to change that, you can use the frame() modifier.

struct ContentView: View {
var body: some View {
ScrollView {
Text("First")
Text("Second")
Text("Third")
Text("Fourth")
Text("Fifth")
Text("Sixth")
Text("Seventh")
Text("Eighth")
Text("Ninth")
Text("Tenth")
.frame(maxWidth: .infinity)
}.font(.largeTitle)
}
}

We set one of the scroll view children to occupy maximum width, making the scroll view occupy the same space. As you can see, the scroll view frame and scroll indicator are push to the edge.

Dynamic Content

Using dynamic content is as easy as static with the help of ForEach.

struct ContentView2: View {
var body: some View {
ScrollView {
ForEach(1..<20) { index in
Text("\(index)")
}
}.font(.largeTitle)
}
}

Mixed Content

You don't have to choose between static or dynamic content. You can mix both content types based on your need.

The following example builds content from both static and dynamic.

struct ContentView2: View {
var body: some View {
ScrollView {
Text("First")
Text("Second")
Text("Third")
ForEach(1..<20) { index in
Text("\(index)")
}
}.font(.largeTitle)
}
}

Nested Content

From our previous example where we try to make a scroll view expand to full width with .frame(maxWidth: .infinity), it works, but it looks kind of ugly to put a frame modifier at a random child view.

struct ContentView: View {
var body: some View {
ScrollView {
Text("First")
Text("Second")
Text("Third")
Text("Fourth")
Text("Fifth")
Text("Sixth")
Text("Seventh")
Text("Eighth")
Text("Ninth")
Text("Tenth")
.frame(maxWidth: .infinity)
}.font(.largeTitle)
}
}

We can create a parent view for all of our content, the same way as a contentView property in UIScrollView, and put a frame modifier there.

In the following example, we wrap content views in a VStack and set frame modifier there.

struct ContentView: View {
var body: some View {
ScrollView {
VStack {
Text("First")
Text("Second")
Text("Third")
Text("Fourth")
Text("Fifth")
Text("Sixth")
Text("Seventh")
Text("Eighth")
Text("Ninth")
Text("Tenth")
}.frame(maxWidth: .infinity) // <1>
}.font(.largeTitle)
}
}

<1> We set frame modifier on the parent view of our content view.

Vertical Scrolling

By default, a scroll view initialize view with Axis.Set.vertical, which will create a vertical scrolling scroll view.

This is what we use in the examples so far.

struct ContentView: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
ForEach(0..<20) { index in
Text("\(index)")
.foregroundColor(.white)
.frame(width: 100, height: 100)
.background(Color.pink)
.cornerRadius(50)
}
}.frame(maxWidth: .infinity)
}.font(.largeTitle)
}
}
By default, the scroll view will create with a vertical scrolling direction.
By default, the scroll view will create with a vertical scrolling direction.

Horizontal Scrolling

To make a scroll view scroll horizontally, you passing Axis.Set.horizontal as axes argument.

struct ContentView: View {
var body: some View {
ScrollView(.horizontal) { // <1>
HStack(spacing: 20) { // <2>
ForEach(0..<20) { index in
Text("\(index)")
.foregroundColor(.white)
.frame(width: 100, height: 100)
.background(Color.pink)
.cornerRadius(50)
}
}.frame(maxHeight: .infinity)
}.font(.largeTitle)
}
}

<1> Set axes as .horizontal to enable horizontal scrolling.
<2> We use HStack instead of VStack here to align our child views horizontally.

You can change the direction of the scroll view by specifying axes argument.
You can change the direction of the scroll view by specifying axes argument.

Setting an axes to .horizontal here doesn't magically change your view layout to horizontal layout. It just makes our scroll view support horizontal axis scrolling.

Let's try .horizontal scroll view with VStack content view.

struct ContentView: View {
var body: some View {
ScrollView(.horizontal) { // <1>
VStack(spacing: 20) { // <2>
ForEach(0..<20) { index in
Text("\(index)")
.foregroundColor(.white)
.frame(width: 100, height: 100)
.background(Color.pink)
.cornerRadius(50)
}
}
}.font(.largeTitle)
}
}

<1> Set axes as .horizontal to enable horizontal scrolling.
<2> We use VStack here.

The result is a content view layout vertically according to VStack, which results in some parts got clip off. The horizontal scroll view doesn't change the layout. It just makes our view support horizontal scrolling.

View layout vertically while scrolling horizontally.
View layout vertically while scrolling horizontally.

Multiple Axes

Since the scroll view parameter is Axis.Set, which is a set, we can support scrolling in both axes simultaneously using [.horizontal, .vertical].

Here is an example where I nested HStack view in a VStack to present a grid like views.

struct ContentView: View {
var body: some View {
ScrollView([.horizontal, .vertical]) { // <1>
VStack(spacing: 20) { // <2>
ForEach(0..<20) { row in
HStack { // <3>
ForEach(0..<20) { column in
Text("\(row),\(column)") // <4>
.foregroundColor(.white)
.frame(width: 100, height: 100)
.background(Color.pink)
.cornerRadius(50)
}
}
}
}.frame(maxWidth: .infinity)
}.font(.largeTitle)
}
}

<1> Set axes to [.horizontal, .vertical].
<2> The root of the content view is VStack.
<3> For each row of the vertical stack, we render a horizontal layout content with HStack.
<4> The innermost view is a text view with the content of row and column number.

Scroll view with two-directional scrolling support.
Scroll view with two-directional scrolling support.

Showing/Hiding scroll indicator

In UIScrollView, we have showsHorizontalScrollIndicator and showsVerticalScrollIndicator property to controls whether the scroll indicator is visible or not. In SwiftUI, scroll view we can control with showsIndicators parameter.

ScrollView(showsIndicators: false) {
....
}

Conclusion

This article shows how easy it is to put our content view into a scroll view and make a scrollable list out of it. In the next article, we will visit another view with a similar capability, List.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Read more article about SwiftUI, List, Navigation, ScrollView, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
What is @Environment in SwiftUI

Learn how SwiftUI shares application settings and preference values across the app.

Next
How to create custom operators and do operators overloading in Swift

Learn how to overload existing operators such as +, -, *, / or create a custom one (such as .^.).

← Home