Last active
February 8, 2026 15:36
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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() | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // 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() | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example
scroll-view-example.mov