How it works
A ScrollTargetBehaviorContext carries the read-only state that a scroll view hands to a custom ScrollTargetBehavior each time it resolves where a scroll should come to rest. When you implement updateTarget(_:context:), the context is your window into everything the behavior needs to make that decision — the size of the visible region, the size of the scrollable content, the scrollable axes, and the environment in effect. Reach for it whenever a snapping or paging rule has to be computed relative to the current layout rather than hardcoded, so the same behavior adapts to any container size.
Receive the context in updateTarget(_:context:)
SwiftUI constructs a
ScrollTargetBehaviorContextand passes it as thecontextargument to your behavior'supdateTarget(_:context:)method; you never create one yourself. InSnapToHalf, the method signaturefunc updateTarget(_ target: inout ScrollTarget, context: ScrollTargetBehaviorContext)is where the value arrives, alongside theinout targetyou adjust.Read the viewport with containerSize
The
containerSizeproperty reports the dimensions of the scroll view's visible region, letting you express targets as multiples of a page or fraction of the screen.SnapToHalfpullscontext.containerSize.widthintopageand uses it both to index the nearest page,(target.rect.minX / page).rounded(), and to recompute the resting offset,p * page.Account for the scrollable axes
Because a
ScrollTargetBehaviorContextdescribes whichever axes the enclosing scroll view scrolls, your math should reference the dimension that actually moves. Here the scroll view is created withScrollView(.horizontal), so the behavior reasons about the width ofcontainerSizeand writes back totarget.rect.origin.x.Use the content metrics to bound the target
Beyond the viewport, the context also surfaces the overall content size and related layout information, which you can consult to clamp or align a target against the real extent of the scrollable content.
SnapToHalfkeeps things minimal — guarding withif page > 0before dividing — but the same context is where you would read content bounds for a behavior that needs to avoid overscrolling.Attach the behavior so the context gets supplied
A
ScrollTargetBehaviorContextonly exists once a behavior is installed on a scroll view, which is done with thescrollTargetBehavior(_:)modifier. Applying.scrollTargetBehavior(SnapToHalf())to theScrollViewis what causes SwiftUI to populate the context and invokeupdateTarget(_:context:)at the end of each scroll.
(target.rect.minX / page).rounded() with (target.rect.minX / page).rounded(.down) to see how reading context.containerSize.width makes every gesture settle on the start of the current page instead of the nearest one.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 SnapToHalf: ScrollTargetBehavior {
func updateTarget(_ target: inout ScrollTarget, context: ScrollTargetBehaviorContext) {
// context exposes the viewport size, content size, axes, etc.
let page = context.containerSize.width
if page > 0 {
let p = (target.rect.minX / page).rounded()
target.rect.origin.x = p * page
}
}
}
struct ScrollTargetBehaviorContextDemo: View {
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 12) {
ForEach(0..<6) { i in
RoundedRectangle(cornerRadius: 12)
.fill(Color.blue.opacity(0.7))
.frame(width: 120, height: 160)
.overlay(Text("Card \(i + 1)").foregroundStyle(.white))
}
}
.padding()
}
.scrollTargetBehavior(SnapToHalf())
}
}