scrollTargetBehavior, new in iOS 17, controls where a ScrollView settles when the user lets
go: full pages, aligned views, or a rule you write yourself.
Paging and View Alignment
.paging snaps by the container size, one page per swipe. .viewAligned decelerates until the
first visible item aligns with the viewport edge.
ScrollView {
ForEach(0..<100) { number in
Text(verbatim: String(number))
.frame(maxWidth: .infinity, minHeight: 300)
}
}
.scrollTargetBehavior(.paging)
Mark the Target Layout
With a lazy container inside the scroll view, viewAligned needs to know which views are snap
targets. Put scrollTargetLayout on the LazyVStack, LazyHStack, or LazyVGrid.
ScrollView(.horizontal) {
LazyHStack {
ForEach(0..<100) { number in
Text(verbatim: String(number))
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
Only the marked container participates: a header before it and a footer after it stay ordinary
content. The behavior follows whichever axes the ScrollView enables.
Custom Behaviors
Conform to ScrollTargetBehavior and implement updateTarget. Mutate the inout
ScrollTarget rect to choose the landing position; the context supplies velocity, axes,
container size, content size, and the original target.
struct CustomScrollTargetBehavior: ScrollTargetBehavior {
func updateTarget(_ target: inout ScrollTarget, context: TargetContext) {
if context.velocity.dy > 0 {
target.rect.origin.y = context.originalTarget.rect.maxY
} else if context.velocity.dy < 0 {
target.rect.origin.y = context.originalTarget.rect.minY
}
}
}
extension ScrollTargetBehavior where Self == CustomScrollTargetBehavior {
static var custom: CustomScrollTargetBehavior { .init() }
}
Then apply it like the built-ins: .scrollTargetBehavior(.custom).