iOS ScrollView and make it snappy!
In iOS17, Swift UI has some new modifiers which make it easier to implement snap behaviour in scrollviews.
ScrollView
Let’s make a simple scrollview:
import SwiftUI
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(0..<20) { i in
TileView(title: String(i))
}
}
.padding()
}
}}
struct TileView: View {
let title: String
var body: some View {
Text(title)
.foregroundStyle(.white)
.frame(width: 150, height: 100)
.background(.purple)
.clipShape(.rect(cornerRadius: 20))
}
}
#Preview {
ContentView()
}
Reminder: Using LazyHStack
instead of HStack
means the tiles are only loaded when needed and not upfront.
This gives us a simple horizontal scrollview:
The default behaviour is free scrolling, and the view stays wherever it finishes after scrolling:
Paging Behaviour
We can add the modifier .scrollTargetBehavior(.paging)
to our scrollview:
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(0..<20) { i in
TileView(title: String(i))
}
}
.padding()
}
.scrollTargetBehavior(.paging)
}
}
This means the scrollview will scroll one screen width at a time and snaps nicely to each page:
View Aligned Behaviour
Instead of using paging, we can use the modifier .scrollTargetBehavior(.viewAligned)
along with .scrollTargetLayout()
on the view we want to be used for settling.
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(0..<20) { i in
TileView(title: String(i))
}
}
.padding()
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
}
}
This gives us the the following behaviour where the scrollview snaps to the individual views:
Additional Notes
This can all be applied to vertical scrollviews as well. I’ve just used horizontal ones here as examples.
These modifiers are only available in iOS 17 / Xcode 15.
More information and options to experiment with in Apple’s docs https://developer.apple.com/documentation/swiftui/scrolltargetbehavior