Get position of scrollview

With the release of iOS 14, there is a new ScrollViewReader which seems like it can read the current position. However, the docs around this are minimal, and do not explain how to do so. So, what is ScrollViewReader, and how can I get the current position of a scrollview?

Accepted Reply

You can get the position by using GeometryReader to read the scrollView content frame, pass the value using PreferenceKey and observe it:

Code Block swift
struct ContentView: View {
    var body: some View {
            ScrollView {
                ZStack {
                    LazyVStack {
                        ForEach(0...100, id: \.self) { index in
                            Text("Row \(index)")
                        }
                    }
                    GeometryReader { proxy in
                        let offset = proxy.frame(in: .named("scroll")).minY
                        Color.clear.preference(key: ScrollViewOffsetPreferenceKey.self, value: offset)
                    }
                }
            }
            .coordinateSpace(name: "scroll")
            .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
                print(value)
            }
    }
}


The .frame(in: .named("scroll")) is required to get the frame relative to the ScrollView and obtain a 0 offset when at the top, without the height of the safe area. If you try using .frame(in: .global) you will get a non-zero value when the scroll view is at the top (20 or 44, depends on the device).


Add a Comment

Replies

ScrollViewReader actually provides a ScrollViewProxy with a function scrollTo(_ id:, anchor:) that you can use to scroll to the position of a View related to a given id:

Code Block swift
struct ContentView: View {
    @State private var position = 0
    var body: some View {
        VStack {
            HStack {
                Button("Top") { position = 0 }
                Button("Middle") { position = 500 }
                Button("Bottom") { position = 1000 }
            }
            ScrollView {
                ScrollViewReader { proxy in
                    LazyVStack {
                        ForEach(0...1000, id: \.self) { index in
                            Text("Row \(index)")
                        }
                    }
                    .onChange(of: position) { value in
                        withAnimation {
                            proxy.scrollTo(value, anchor: .center)
                        }
                    }
                }
            }
        }
    }
}


I see, thank you. So there is no way to get the current scroll position?
You can get the position by using GeometryReader to read the scrollView content frame, pass the value using PreferenceKey and observe it:

Code Block swift
struct ContentView: View {
    var body: some View {
            ScrollView {
                ZStack {
                    LazyVStack {
                        ForEach(0...100, id: \.self) { index in
                            Text("Row \(index)")
                        }
                    }
                    GeometryReader { proxy in
                        let offset = proxy.frame(in: .named("scroll")).minY
                        Color.clear.preference(key: ScrollViewOffsetPreferenceKey.self, value: offset)
                    }
                }
            }
            .coordinateSpace(name: "scroll")
            .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
                print(value)
            }
    }
}


The .frame(in: .named("scroll")) is required to get the frame relative to the ScrollView and obtain a 0 offset when at the top, without the height of the safe area. If you try using .frame(in: .global) you will get a non-zero value when the scroll view is at the top (20 or 44, depends on the device).


Add a Comment
So ScrollViewREADER is not able to read the current offset of the scrollview? That's weird...
link: https://stackoverflow.com/questions/62588015/swiftui-get-current-scroll-position-from-scrollview

Code Block
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 {
Color.clear.preference(key: ViewOffsetKey.self,
value: -$0.frame(in: .named("scroll")).origin.y)
})
.onPreferenceChange(ViewOffsetKey.self) { print("offset >> \($0)") }
}.coordinateSpace(name: "scroll")
}
}
struct ViewOffsetKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}


a real, working example with a nice implementation is here:

https://swiftuirecipes.com/blog/swiftui-scrollview-scroll-offset