TechnologiesSwiftUI

AnyScrollTargetBehavior struct

iOSmacOStvOSwatchOSvisionOSiOS 18.0+✓ renders

A type-erased scroll target behavior.

How it works

AnyScrollTargetBehavior is a type-erased wrapper around any value that conforms to the ScrollTargetBehavior protocol. A scroll target behavior decides where a scroll view comes to rest once a drag or fling ends — for example, snapping to the leading edge of the nearest view rather than stopping at an arbitrary offset. Because concrete behaviors such as view-aligned and paging snapping have different underlying types, AnyScrollTargetBehavior gives you a single, uniform type to store, pass around, or return from a function when the specific behavior is chosen at runtime. Reach for it whenever you need to treat differing behaviors interchangeably while still applying them through the standard scrollTargetBehavior(_:) modifier.

  1. Identify the snap targets with scrollTargetLayout()

    A scroll target behavior can only align to candidates it knows about. Applying scrollTargetLayout() to the container that holds the repeating content — here the HStack of colored cards inside the ScrollView — marks each of its subviews as an individual scroll target that the behavior can snap to.

  2. Attach the behavior with scrollTargetBehavior(_:)

    The scrollTargetBehavior(_:) modifier on the ScrollView is where any ScrollTargetBehavior takes effect. In the example it receives .viewAligned, and SwiftUI wraps that concrete behavior in an AnyScrollTargetBehavior so the modifier can accept it uniformly alongside any other behavior.

  3. Erase a concrete behavior into AnyScrollTargetBehavior

    When you hold a behavior whose exact type varies — say you compute whether to use .viewAligned or .paging based on state — you wrap it in AnyScrollTargetBehavior so every branch yields the same type. The result still flows into scrollTargetBehavior(_:) exactly like the .viewAligned value does in the example.

  4. Conform to ScrollTargetBehavior

    AnyScrollTargetBehavior conforms to the same ScrollTargetBehavior protocol it wraps, forwarding the resting-position calculation to the value inside it. That conformance is what lets the erased value drive the snapping of the cards laid out by scrollTargetLayout() without the call site needing to know which concrete behavior is in use.

Try it — Change .scrollTargetBehavior(.viewAligned) to .scrollTargetBehavior(.paging) and watch the scroll view snap a full page at a time instead of aligning to each card.

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.

AnyScrollTargetBehavior.swift
struct AnyScrollTargetBehaviorDemo: View {
    let colors: [(String, Color)] = [("Red", .red), ("Green", .green), ("Blue", .blue), ("Orange", .orange)]
    var body: some View {
        ScrollView(.horizontal) {
            HStack(spacing: 16) {
                ForEach(colors, id: \.0) { name, color in
                    RoundedRectangle(cornerRadius: 16)
                        .fill(color)
                        .frame(width: 200, height: 160)
                        .overlay(Text(name).font(.title2).bold().foregroundStyle(.white))
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.viewAligned)
        .padding()
    }
}
Live preview
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →