Apr 19, 2024 iOS

Get the current scroll position of a SwiftUI ScrollView

Certainly! In SwiftUI, you can obtain the current scroll position of a ScrollView using various techniques. Let’s explore a couple of approaches:

  1. Using GeometryReader and PreferenceKey: You can track the scroll position by using a custom PreferenceKey. Here’s an example:
struct DemoScrollViewOffsetView: View {
    @State private var offset = CGFloat.zero

    var body: some View {
        ScrollView {
            VStack {
                ForEach(0..<100) { i in
                    Text("Item \(i)").padding()
                }
            }
            .background(GeometryReader { proxy in
                Color.clear.preference(key: ViewOffsetKey.self, value: -$0.frame(in: .named("scroll")).origin.y)
            })
            .onPreferenceChange(ViewOffsetKey.self) { newOffset in
                print("Current scroll offset: \(newOffset)")
            }
        }
        .coordinateSpace(name: "scroll")
    }
}

struct ViewOffsetKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue: CGFloat = .zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value += nextValue()
    }
}
  1. In this example, the DemoScrollViewOffsetView captures the scroll position using the ViewOffsetKey preference key. The onPreferenceChange modifier allows you to react to changes in the offset.
  2. Using onChange within GeometryReader: Another approach is to use GeometryReader within the ScrollView and detect changes in the scroll position using the onChange modifier. Here’s a simplified version:
struct ContentView: View {
    @State private var scrollOffset: CGFloat = 0

    var body: some View {
        ScrollView {
            GeometryReader { geometry in
                Color.clear
                    .preference(key: ViewOffsetKey.self, value: geometry.frame(in: .named("scroll")).origin.y)
            }
            .onPreferenceChange(ViewOffsetKey.self) { newOffset in
                scrollOffset = newOffset
                print("Current scroll offset: \(scrollOffset)")
            }

            VStack {
                ForEach(0..<100) { i in
                    Text("Item \(i)").padding()
                }
            }
        }
        .coordinateSpace(name: "scroll")
    }
}
  1. In this version, the GeometryReader captures the scroll position, and the onChange modifier updates the scrollOffset.

Remember that these examples are simplified for demonstration purposes. Depending on your specific use case, you may need to adapt the code to suit your requirements. Happy scrolling! 🚀📜


I provided two approaches for obtaining the scroll position in a SwiftUI ScrollView. If you have any further questions or need additional assistance, feel free to ask! 😊

SwiftUI doesn’t provide a direct API to get the current scroll position of a ScrollView out of the box. However, you can achieve this by using a GeometryReader combined with a ScrollViewReader.

Here’s a basic implementation:

import SwiftUI

struct ContentView: View {
    let items = (1...100).map { "Item \($0)" }
    @State private var scrollPosition: CGFloat = 0

    var body: some View {
        VStack {
            ScrollViewReader { proxy in
                ScrollView {
                    LazyVStack {
                        ForEach(items, id: \.self) { item in
                            Text(item)
                                .padding()
                        }
                    }
                    .onChange(of: scrollPosition) { newValue in
                        // Update scroll position
                        scrollPosition = newValue
                    }
                    .onChange(of: items.count) { _ in
                        // Scroll to bottom when item count changes
                        withAnimation {
                            proxy.scrollTo(items.last, anchor: .bottom)
                        }
                    }
                }
                .onAppear {
                    // Scroll to saved position
                    proxy.scrollTo(scrollPosition, anchor: .top)
                }
                .coordinateSpace(name: "scroll")
            }
            
            Text("Scroll position: \(scrollPosition)")
        }
    }
}

In this example:

  • We use a ScrollViewReader to access the ScrollView.
  • Inside the ScrollViewReader, we have a ScrollView with a LazyVStack containing some items.
  • We use .onChange to detect changes in the scrollPosition state variable and update it accordingly.
  • When the view appears, we use proxy.scrollTo to scroll to the saved scrollPosition.
  • We also use .onChange to scroll to the bottom of the list when the number of items changes.

Remember, this implementation may not work perfectly in all scenarios, especially when dealing with complex layouts or when the content dynamically changes size. Adjustments may be needed based on your specific requirements.