How to use SwiftUI ScrollView

Posted On Feb 02, 2023 |

Learn how to use SwiftUI ScrollView, a scrollable container view in SwiftUI. Explore its features that allow you to scroll horizontally or vertically

In this blog post, I will show you how to use scroll view, including vertical and horizontal scrolling, how to programmatically scroll, and how to create very complex layout views. I will show you a lot of different examples and use cases. So that by the end of this blog, you will want to start using ScrollView in your next iOS project and build amazing-looking UI.

Download the project files from here ⬇️

.

.

.

What is a ScrollView

The scroll view is a scrollable component in SwiftUI. It is a container view that shows its content either in a horizontal or vertical scroll direction. It does not allow to zoom in or out of its content.

.

What is the difference between ScrollView and List in SwiftUI?

SwiftUI provides developers with two ways to create scrollable content: List and ScrollView. ScrollView is the more versatile option both in terms of custom styling as well as adjusting scroll behavior. For example, ScrollView allows for programmatical scrolling, set a horizontal scroll view, show grid views and create sections with different scroll axis similar to UICollectionView in UIKit.  >>> Read about SwiftUI List

.

Showing Large Data Sets

The List view is more efficient when it comes to memory usage due to its ability to lazy load only what is necessary as you scroll, while a ScrollView loads all content at once into memory. When working with large data sets inside a ScrollView, make use of LazyVStack and LazyHStack. These load their data lazily similar to List.

.

Options for Custom Styling

When it comes to customizing the styling, ScrollView is more versatile while List gives you a good default appearance right out of the box, which is adjusted for the different Apple platforms. If you are looking to make a cross-platform app, you can take advantage of List to quickly create a great-looking UI.

.

How to add content to ScrollView?

To add content to ScrollView, you can embed your SwiftUI views in a ScrollView. Per default, it will show the subviews like a vertical stack. A typical use case is if you want to fit your content on the iPhone screen, especially for smaller screen sizes or for larger dynamic types. In the following example, I embed the view in a ScrollView, which makes it easily adaptable for most situations:

struct ContentView: View {
    let description = "Lorem ipsum ..."
    
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 20) {
                Image("minime")
                    .resizable()
                    .scaledToFill()
                    .frame(width: 350, height: 350)
                    .clipped()
                
                Text("Mini-me")
                    .font(.largeTitle)
                
                Text(description)
                    .multilineTextAlignment(.leading)
            }
            .padding()
        }
    }
}
SwiftUI ScrollView used for fiting the content on smaller iphone screens or larger dynamic type

.

You can add any type of SwiftUI view to the ScrollView. For example LazyVStack, LazyHStack, LazyVGrid, or LazyHGrid. For example, we can make a vertical grid veritcally scrollable:

Example for SwiftUI ScrollView with grid layout using LazyVGrid

.

Example Data

Because you can create some great-looking UI with ScrollView, I am using more complex data, where I added images in the assets catalog (how to work with images):

struct NatureInspiration: Identifiable {
    let name: String
    let imageName: String
    let description: String
    let id = UUID()
    
    static func examples() -> [NatureInspiration] {
        [NatureInspiration(name: "Desert",
                           imageName: "desert",
                           description: "A desert is a barren area of landscape where little precipitation occurs and, consequently, living conditions are hostile for plant and animal life."),
         ...
        ]
    }
}

I also defined 3 different cells for NatureInspiration to have some variety in the sample project. The following defines a Card view:

struct InspirationCardView: View {
    
    let inspiration: NatureInspiration
    let padding: CGFloat = 10
    
    var body: some View {
        Image(inspiration.imageName)
            .resizable()
            .scaledToFit()
            .cornerRadius(10)
            .shadow(radius: 5)
        
            .overlay(alignment: .bottomTrailing, content: {
                Text(inspiration.name)
                    .bold()
                    .foregroundColor(Color.white)
                    .padding()
            })
    }
}

The size of the images is adjusted to fit the available space. You can learn more about how to work with images in this post.

.

Scrollable Axis

Scroll view has a parameter to change the scroll direction from the default vertical scrolling to horizontal. We also have to use a horizontal stack inside the ScrollView otherwise the layout directions don't match. You could use LazyHStack or LazyHGrid.

struct ContentView: View {
    let natureInspiration = NatureInspiration.examples()
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(spacing: 10) {
                ForEach(natureInspiration) { inspiration in
                    InspirationCardView(inspiration: inspiration)
                }
            }
            .padding()
        }
        .frame(height: 200)
    }
}
SwiftUI ScrollView examples with horizontal and vertical axis


.

Complex Layout Example

We can combine multiple ScrollViews together to create a complex layout similar to UICollectionView. It is possible to add ScrollView as a child view to a parent scrollview. You can create sections with horizontal scroll views by adding the following to the parent view:

ScrollView {
    ScrollView(.horizontal) {
        LazyHStack(spacing: 10) {
            ForEach(natureInspiration) { inspiration in
                InspirationCardView(inspiration: inspiration)
            }
        }
        
        // Add more sections
    }
}
SwiftUI Scrollview example with section that scroll horizontal and grid layouts

.

Scroll Behaviour

Scroll indicator

The scroll view displays per default scroll indicators. If you want to hide the scroll indicator, you can pass false for the argument showsIndicators:

ScrollView(showsIndicators: false) {
   ...
}

.

How do I disable scrolling?

With iOS 16, we can now disable scrolling for any scrollable component including ScrollView, List, and TextEditor. Use the new modifier scrollDisabled(_:). You can also dynamically enable/disable this behaviour.

ScrollView {
}
 .scrollDisabled(true)

.

Programmatic scrolling with ScrollViewReader

With iOS 14 and macOS 11, ScrollViewReader was introduced, which gives you the ScrollViewProxy. Call scrollTo(_ id: ID, anchor: UnitPoint) to programmatically scroll to the view with the provided id. I

n the following example, I added an id to the last text view in the scroll view. At the top of the scroll view, you can use the button to scroll down. Programmatic scrolling is done by calling the proxis scrollTo(bottomID). Because I make the change in a withAnimation block, the scrolling animates nicely.

struct ProgrammaticScrollExampleView: View {
    @Namespace var bottomID
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                Button("Scroll to Bottom") {
                    withAnimation {
                        proxy.scrollTo(bottomID)
                    }
                }
                
                // main content
                
                Text("You reached the bottom")
                    .id(bottomID)
            }
        }
    }

.

Scrolling in a dynamic list

In case you have a dynamic list with many data rows, we can set the id like this:

ScrollViewReader { proxy in
      ScrollView {
         ForEach(natureInspiration) { inspiration in
               InspirationRowView(inspiration: inspiration)
                  .id(inspiration.id)
    }
}

You can then scroll to a specific row by using the data´s id:

Button("Scroll to selected inspiration") {
    withAnimation {
        proxy.scrollTo(selectedInspiration.id, anchor: .top)
    }
}

Notice that I used the anchor parameter, where you have the options for top, center, and bottom. The default anchor is center. If you set an anchor of top, the top of the scroll views frame is aligned to the top of the view you want to scroll to. If the scrollable content region is not large enough. For example, scroll to the last cell and use a top anchor, the scroll view will not scroll fully to the defined position.

Example implementation for scroll to in a SwiftUI ScrollView

.

In order to see the alignment better, I added an overlay with red lines. You can see the frame of the scroll view and the center line.

ScrollViewReader { proxy in
    ScrollView {
        ...
    }
}
.overlay {
    VStack(spacing: 0) {
        Rectangle().stroke(Color.pink, lineWidth: 2)
        Rectangle().stroke(Color.pink, lineWidth: 2)
    }
}

SwiftUI scrollview examples where ScrollViewReader is used to scroll to particular row with differnt anchor settings
When you tap on a button the list scrolls programmatically to the selected row (highlighted in grey).


.

How to get ScrollView offset in SwiftUI?

ScrollView does not have a native way to give you the content offset of a scroll view. I added a solution with GeometryReader and Preference Keys in the project files. I made it so that you can The example makes the header area smaller when the user scrolls down.

SwiftUI example where the scroll offset of a ScrollView is used to animate the header

.

How to pin a section header to the top of a scroll view?

You can use a LazyVStack inside a ScrollView and specify its argument pinnedViews, which has the option to use section headers or footers:

ScrollView {
    LazyVStack(alignment: .leading, spacing: 10, pinnedViews: .sectionHeaders) {
        Section {
            ForEach(inspiration) { inspiration in
                InspirationRowView(inspiration: inspiration)
            }
        } header: {
            SectionHeaderView(title: "First Section")
        }
        ....
    }
    
}


.

Paging Behaviour

Scroll views very commonly have page behavior, which means that when the user releases scrolling the view snaps into the next page position. This is currently not supported.

It is possible to use the open-source framework Introspect to set paging.

But you will only get paging to the full width of the scroll view. This is only possible with UICollectionView:

SwiftUI ScrollView paging set with IntroSpect

.

Conclusion

We have looked into ScrollViews and how to scroll programmatically, disable scrolling, get the current offset, pin headers, and use Introspect to get paging behavior. ScrollViews are a great way to scroll through your content and keep it organized in sections. With a wide range of options, you can customize the look and feel of your app.

Happy coding! :)

.

Download the project files from here ⬇️

.

Further Reading:

.

Categories: SwiftUI, Swiftui components