How it works
ScrollTargetBehavior is the protocol that lets you control where a scroll view comes to rest. By default a scroll view stops wherever momentum carries it, but conforming behaviors can intercept that resting position and align it to a meaningful target, such as the top of a page or the leading edge of an item. Reach for it when free-form scrolling isn't enough and you need content to settle on predictable boundaries, and apply it with the scrollTargetBehavior(_:) modifier. SwiftUI ships built-in behaviors like paging and viewAligned, and you can define your own by conforming a type to the protocol.
Attach a behavior with scrollTargetBehavior(_:)
The scrollTargetBehavior(_:) modifier is where a ScrollTargetBehavior plugs into a scroll view. It takes a behavior value and applies it to the enclosing scroll container, governing how the scroll resolves after a gesture ends. Here it is placed on the
ScrollView(.horizontal)as.scrollTargetBehavior(.paging).Choose the built-in paging behavior
The
.pagingbehavior is a concrete ScrollTargetBehavior that snaps the scroll view one container-sized page at a time, so each swipe advances by the visible width or height rather than a free distance. It is the value passed toscrollTargetBehaviorin the example, turning the row ofRoundedRectanglecards into discrete pages.Mark the snap candidates with scrollTargetLayout()
Behaviors that align to individual views, such as viewAligned, need to know which subviews are valid targets. The scrollTargetLayout() modifier identifies the layout container whose children should act as scroll targets. In the example it is applied to the
HStackof cards via.scrollTargetLayout(), exposing eachForEachitem as a candidate the behavior can align to.Define a custom behavior via the protocol requirement
To build your own alignment logic, conform a type to ScrollTargetBehavior and implement updateTarget(_:context:), which receives the proposed resting target and the scroll context and lets you rewrite the target's rect or anchor. The built-in
.pagingvalue used here is one such conforming behavior; a custom type would replace it in the samescrollTargetBehaviorcall.
.scrollTargetBehavior(.paging) for .scrollTargetBehavior(.viewAligned) and watch the scroll settle on each individual card's leading edge instead of jumping a full page at a time.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct ScrollTargetBehaviorDemo: View {
let colors: [(String, Color)] = [
("Red", .red), ("Green", .green), ("Blue", .blue)
]
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 16) {
ForEach(colors, id: \.0) { name, color in
RoundedRectangle(cornerRadius: 16)
.fill(color)
.frame(width: 220, height: 160)
.overlay(Text(name).font(.title).foregroundStyle(.white))
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
.padding()
}
}