Skip to content

Instantly share code, notes, and snippets.

@fxm90
Last active February 8, 2026 15:36
Show Gist options
  • Select an option

  • Save fxm90/5bc949e4d6f2f56901b47250a25fc64d to your computer and use it in GitHub Desktop.

Select an option

Save fxm90/5bc949e4d6f2f56901b47250a25fc64d to your computer and use it in GitHub Desktop.
A SwiftUI example demonstrating how to track the horizontal offset in a scroll view.
//
// ContentView.swift
//
// Created by Felix Mau on 07.02.26.
// Copyright © 2026 Felix Mau. All rights reserved.
//
import SwiftUI
private enum Config {
/// The name of the coordinate space used to measure the scroll view’s content
/// relative to the scroll view itself.
static let scrollViewCoordinateSpace = "scrollViewCoordinateSpace"
}
/// A view that demonstrates how to observe the horizontal scroll offset
/// of a `ScrollView` in SwiftUI using a named coordinate space and geometry updates.
struct ContentView: View {
/// The current horizontal scroll offset of the scroll view’s content.
///
/// This value updates as the user scrolls. A negative value indicates
/// scrolling to the right, while zero means the content is aligned
/// with the leading edge.
@State
private var horizontalScrollOffset: CGFloat = 0 {
didSet {
// Useful for debugging or driving side effects such as animations.
print("Horizontal scroll offset", horizontalScrollOffset)
}
}
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(0 ..< 3) { index in
VStack(alignment: .leading) {
Text("Hello World \(index + 1)")
.font(.headline)
Text("Lorem ipsum dolor sit amet.")
.font(.body)
}
.frame(width: 375, alignment: .leading)
}
}
// Observe geometry changes for the scroll content.
//
// This modifier extracts the `minX` value of the content's frame
// within the named coordinate space, which effectively represents
// the horizontal scroll offset.
.onGeometryChange(for: CGFloat.self) { geometry in
geometry.frame(in: .named(Config.scrollViewCoordinateSpace)).minX
} action: { horizontalScrollOffset in
self.horizontalScrollOffset = horizontalScrollOffset
}
}
.scrollIndicators(.hidden)
// Define a named coordinate space so we can measure the scroll content
// relative to the scroll view itself.
.coordinateSpace(name: Config.scrollViewCoordinateSpace)
.frame(width: 375, height: 80)
}
}
// MARK: - Preview
#Preview {
ContentView()
}
//
// ContentViewWithSnapping.swift
//
// Created by Felix Mau on 07.02.26.
// Copyright © 2026 Felix Mau. All rights reserved.
//
import SwiftUI
private enum Config {
/// The name of the coordinate space used to measure the scroll view’s content
/// relative to the scroll view itself.
static let scrollViewCoordinateSpace = "scrollViewCoordinateSpace"
}
/// A view that demonstrates how to observe the horizontal scroll offset
/// of a `ScrollView` in SwiftUI using a named coordinate space and geometry updates.
///
/// This example further demonstrates how to implement a **snapping behavior** by setting
/// the scroll target behavior to `.viewAligned`.
struct ContentViewWithSnapping: View {
/// The current horizontal scroll offset of the scroll view’s content.
///
/// This value updates as the user scrolls. A negative value indicates
/// scrolling to the right, while zero means the content is aligned
/// with the leading edge.
@State
private var horizontalScrollOffset: CGFloat = 0 {
didSet {
// Useful for debugging or driving side effects such as animations.
print("Horizontal scroll offset", horizontalScrollOffset)
}
}
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(0 ..< 3) { index in
VStack(alignment: .leading) {
Text("Hello World \(index + 1)")
.font(.headline)
Text("Lorem ipsum dolor sit amet.")
.font(.body)
}
.frame(width: 375, alignment: .leading)
}
}
// Marks the stack’s children as scroll targets.
//
// This tells SwiftUI that each child view inside the layout
// can be snapped to when used in combination with the view modifier
// `scrollTargetBehavior`.
.scrollTargetLayout()
// Observe geometry changes for the scroll content.
//
// This modifier extracts the `minX` value of the content's frame
// within the named coordinate space, which effectively represents
// the horizontal scroll offset.
.onGeometryChange(for: CGFloat.self) { geometry in
geometry.frame(in: .named(Config.scrollViewCoordinateSpace)).minX
} action: { horizontalScrollOffset in
self.horizontalScrollOffset = horizontalScrollOffset
}
}
.scrollIndicators(.hidden)
// Enables snapping behavior when scrolling ends.
//
// The `.viewAligned` behavior causes the scroll view to settle
// on the nearest child view that was marked as a scroll target
// via `.scrollTargetLayout()`.
//
// In this example, each fixed-width child view snaps neatly
// into alignment as the user lifts their finger.
.scrollTargetBehavior(.viewAligned)
// Define a named coordinate space so we can measure the scroll content
// relative to the scroll view itself.
.coordinateSpace(name: Config.scrollViewCoordinateSpace)
.frame(width: 375, height: 80)
}
}
// MARK: - Preview
#Preview {
ContentViewWithSnapping()
}
@fxm90
Copy link
Author

fxm90 commented Feb 8, 2026

Example

scroll-view-example.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment