SwiftUI.cc
Snippets
LayoutScrollingIntermediate

SwiftUI ScrollView Paging, Snapping, and Content Margins

Modern ScrollView is configured declaratively: scrollTargetBehavior for paging and snapping, scrollPosition for tracking, contentMargins for insets, plus bounce and disable controls.

5 min readUpdated 2026-06
Snapping color carousel with position tracking
import SwiftUI

struct ColorPager: View {
    @State private var current: Int?

    var body: some View {
        VStack {
            ScrollView(.horizontal) {
                LazyHStack(spacing: 16) {
                    ForEach(0..<6, id: \.self) { index in
                        RoundedRectangle(cornerRadius: 24)
                            .fill(.indigo.gradient)
                            .containerRelativeFrame(.horizontal)
                            .frame(height: 220)
                            .id(index)
                    }
                }
                .scrollTargetLayout()
            }
            .scrollTargetBehavior(.paging)
            .scrollPosition(id: $current)
            .contentMargins(.horizontal, 20, for: .scrollContent)

            Text("Page \((current ?? 0) + 1) of 6")
                .font(.caption)
        }
    }
}

ScrollView snapping preview

Declarative scroll configuration

ScrollView gained a family of modifiers that replace most UIScrollView delegate work. The pattern: describe the geometry (scrollTargetLayout, contentMargins), the physics (scrollTargetBehavior, scrollBounceBehavior), and the state (scrollPosition), then let the system drive.

Snapping

Two built-in behaviors cover carousels:

.scrollTargetBehavior(.paging)       // one page per swipe
.scrollTargetBehavior(.viewAligned)  // settle on item edges

.viewAligned needs to know what an "item" is — apply .scrollTargetLayout() to the LazyHStack or LazyVStack whose children are the snap targets.

Tracking and driving position

.scrollPosition(id: $current) binds the leading visible item's identity. Write to the binding (optionally inside withAnimation) to scroll programmatically; read it to update page indicators. For pixel-level needs, ScrollPosition also supports offsets and edges.

Margins, bounce, and disabling

contentMargins(.horizontal, 20, for: .scrollContent) insets the content while letting indicators hug the screen edge. scrollBounceBehavior(.basedOnSize) stops short content from rubber-banding. scrollDisabled(true) freezes scrolling while a drag gesture or edit mode owns the touch.

Common mistakes

  • Forgetting scrollTargetLayout, so viewAligned has nothing to align to.
  • Padding the content instead of using contentMargins, which clips shadows at the edges.
  • Tracking position with GeometryReader when scrollPosition already reports it.

Related reference