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 ⬇️
.
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.
.
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.
.
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() } } }
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:
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.
.
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) } }
.
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 } }
.
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) { ... }
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)
.
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) } } }
.
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.
.
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) } }
.
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.
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") } .... } }
.
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:
.
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