SwiftUI makes it easy to create smooth and interactive scrolling in your apps. You can use tools like ScrollView
, ScrollViewReader
, and ScrollViewProxy
to handle large amounts of data, create unique scrolling effects, and even programmatically jump to specific parts of your content.
In this article, we’ll dive deep into how these tools work and build a simple app to show you what they can do.
ScrollView: The Basics
A ScrollView lets you see more content than fits on the screen by letting you scroll up and down or side-to-side. Here’s a quick example of how it works.
import SwiftUI
struct SimpleScrollView: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
ForEach(1...50, id: \.self) { index in
Text("Item \(index)")
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue.opacity(0.2))
.cornerRadius(10)
.padding(.horizontal)
}
}
}
}
}
SwiftKey Features:
- You can easily adjust a ScrollView to scroll horizontally, vertically, or in both directions.
- It’s smart enough to automatically adjust to fit perfectly on any screen size
ScollViewReader: Enabling Programmatic Scrolling
Imagine you have a long list of items on your screen. ScrollViewReader lets you easily jump directly to the one you’re looking for.
It works by teaming up with ScrollViewProxy. This little helper gives you the tools to quickly move the screen to show the exact item you want to see.
Example: Scrolling to a Specific Item
import SwiftUI
struct ScrollViewReaderExample: View {
var body: some View {
ScrollViewReader { proxy in
VStack {
Button("Scroll to Item 25") {
proxy.scrollTo(25, anchor: .center)
}
.padding()
ScrollView {
VStack(spacing: 20) {
ForEach(1...50, id: \.self) { index in
Text("Item \(index)")
.id(index) // Assign a unique ID for each item
.frame(maxWidth: .infinity)
.padding()
.background(Color.green.opacity(0.2))
.cornerRadius(10)
.padding(.horizontal)
}
}
}
}
}
}
}
SwiftHow It Works:
id
Modifier : Each item in your scrolling list has a unique identifier within theScrollView
scrollTo
Method : You can tell theScrollView
tojump
directly to the item with the name you specify.
ScrollViewProxy: The Core of Programmatic Control
ScrollViewProxy
is a powerful tool that lets you control how your scrolling content behaves. It gives you the power to:
- Jump directly to specific items: Think of it like choosing a chapter in a book.
- Position content perfectly: You can easily make sure the most important part is always at the
top
,center
, orbottom
of the screen.
proxy.scrollTo(itemID, anchor: .top)
SwiftSample Project: A Beautiful ScrollView Page
Now let’s put these ideas together! We’ll build a beautiful page where users can easily jump between different sections by simply tapping a button.
//
// ContentView.swift
// scrollviewTest
//
// Created by mohsen on 12/24/24.
//
import SwiftUI
struct ContentView: View {
var body: some View {
ScrollViewReader { proxy in
VStack(spacing: 20) {
// Navigation Bar
HStack(spacing: 10) {
Button(action: {
withAnimation {
proxy.scrollTo("section1", anchor: .top)
}
}) {
Text("Section 1")
.padding()
.frame(maxWidth: .infinity)
.background(LinearGradient(gradient: Gradient(colors: [.red, .pink]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(10)
.foregroundColor(.white)
.font(.headline)
.shadow(color: .pink.opacity(0.5), radius: 5, x: 0, y: 3)
}
Button(action: {
withAnimation {
proxy.scrollTo("section2", anchor: .top)
}
}) {
Text("Section 2")
.padding()
.frame(maxWidth: .infinity)
.background(LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(10)
.foregroundColor(.white)
.font(.headline)
.shadow(color: .blue.opacity(0.5), radius: 5, x: 0, y: 3)
}
Button(action: {
withAnimation {
proxy.scrollTo("section3", anchor: .top)
}
}) {
Text("Section 3")
.padding()
.frame(maxWidth: .infinity)
.background(LinearGradient(gradient: Gradient(colors: [.green, .teal]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(10)
.foregroundColor(.white)
.font(.headline)
.shadow(color: .green.opacity(0.5), radius: 5, x: 0, y: 3)
}
}
.padding()
.background(Color.white.opacity(0.8))
.cornerRadius(15)
.shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5)
// Scrollable Content
ScrollView {
VStack(spacing: 50) {
BeautifulSectionView(title: "Section 1", color: .red, gradient: Gradient(colors: [.red, .orange]), id: "section1")
BeautifulSectionView(title: "Section 2", color: .blue, gradient: Gradient(colors: [.blue, .purple]), id: "section2")
BeautifulSectionView(title: "Section 3", color: .green, gradient: Gradient(colors: [.green, .teal]), id: "section3")
}
}
}
.padding(.horizontal)
.background(
LinearGradient(gradient: Gradient(colors: [.white, .gray.opacity(0.2)]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
)
}
}
}
#Preview {
ContentView()
}
// Custom Section View
struct BeautifulSectionView: View {
let title: String
let color: Color
let gradient: Gradient
let id: String
var body: some View {
VStack(spacing: 20) {
Text(title)
.font(.largeTitle)
.bold()
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(LinearGradient(gradient: gradient, startPoint: .leading, endPoint: .trailing))
.cornerRadius(15)
.shadow(color: color.opacity(0.5), radius: 10, x: 0, y: 5)
Text("""
This is \(title).
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
On the other hand, we denounce with righteous indignation and dislike men who
are so beguiled and demoralized by the charms of pleasure of the moment, so
blinded by desire, that they cannot foresee the pain and trouble that are
bound to ensue; and equal blame belongs to those who fail in their duty
through weakness of will, which is the same as saying through shrinking
from toil and pain. These cases are perfectly simple and easy to
distinguish. In a free hour, when our power of choice is untrammelled and
when nothing prevents our being able to do what we like best, every
pleasure is to be welcomed and every pain avoided. But in certain
circumstances and owing to the claims of duty or the obligations of
business it will frequently occur that pleasures have to be repudiated
and annoyances accepted. The wise man therefore always holds in these
matters to this principle of selection: he rejects pleasures to secure
other greater pleasures, or else he endures pains to avoid worse pains.
""")
.font(.body)
.padding()
.background(Color.white.opacity(0.9))
.cornerRadius(15)
.shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: 5)
}
.id(id) // Assign ID for ScrollViewProxy
.padding(.horizontal)
}
}
SwiftHere’s what makes this page special:
- Easy Navigation: Users can quickly jump between different parts of the page with just a tap.
- Stylish Sections: Each section has its own unique look and feel with different colors and designs.
- Smooth Scrolling: When you tap a button, the page smoothly glides to the section you want to see.
Conclusion
SwiftUI gives you all the tools you need to make great scrolling experiences with ScrollView
, ScrollViewReader
, and ScrollViewProxy
. Whether you’re making a basic scrolling list or a more complicated interface, these tools make it easy and powerful.