TechnologiesSwiftUI

ScrollTargetBehaviorContext struct

iOSmacOStvOSwatchOSvisionOSiOS 17.0+✓ renders

The context in which a scroll target behavior updates its scroll target.

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.

  1. Receive the context in updateTarget(_:context:)

    SwiftUI constructs a ScrollTargetBehaviorContext and passes it as the context argument to your behavior's updateTarget(_:context:) method; you never create one yourself. In SnapToHalf, the method signature func updateTarget(_ target: inout ScrollTarget, context: ScrollTargetBehaviorContext) is where the value arrives, alongside the inout target you adjust.

  2. Read the viewport with containerSize

    The containerSize property reports the dimensions of the scroll view's visible region, letting you express targets as multiples of a page or fraction of the screen. SnapToHalf pulls context.containerSize.width into page and uses it both to index the nearest page, (target.rect.minX / page).rounded(), and to recompute the resting offset, p * page.

  3. Account for the scrollable axes

    Because a ScrollTargetBehaviorContext describes whichever axes the enclosing scroll view scrolls, your math should reference the dimension that actually moves. Here the scroll view is created with ScrollView(.horizontal), so the behavior reasons about the width of containerSize and writes back to target.rect.origin.x.

  4. 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. SnapToHalf keeps things minimal — guarding with if page > 0 before dividing — but the same context is where you would read content bounds for a behavior that needs to avoid overscrolling.

  5. Attach the behavior so the context gets supplied

    A ScrollTargetBehaviorContext only exists once a behavior is installed on a scroll view, which is done with the scrollTargetBehavior(_:) modifier. Applying .scrollTargetBehavior(SnapToHalf()) to the ScrollView is what causes SwiftUI to populate the context and invoke updateTarget(_:context:) at the end of each scroll.

Try it — Replace (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.

ScrollTargetBehaviorContext.swift
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())
    }
}
Live preview
Card \(i + 1) Card \(i + 1) Card \(i + 1) Card \(i + 1) Card \(i + 1) Card \(i + 1)
Card \(i + 1) Card \(i + 1) Card \(i + 1) Card \(i + 1) Card \(i + 1) Card \(i + 1)
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →