28

With the new ScrolLViewReader, it seems possible to set the scroll offset programmatically.

But I was wondering if it is also possible to get the current scroll position?

It seems like the ScrollViewProxy only comes with the scrollTo method, allowing us to set the offset.

Thanks!

bdesham
  • 14,616
  • 11
  • 78
  • 119
Mofawaw
  • 4,544
  • 4
  • 26
  • 58
  • Possible duplicate of this question: https://stackoverflow.com/questions/59342384/how-to-detect-scroll-direction-programmatically-in-swiftui-scrollview – SwiftySteve Jan 08 '21 at 09:03

3 Answers3

48

It was possible to read it and before. Here is a solution based on view preferences.

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()
    }
}

backup

Asperi
  • 173,274
  • 14
  • 284
  • 455
  • 2
    Is it possible to set it? – Ian Warburton Jul 25 '20 at 20:02
  • 5
    @Asperi - if I enclose `DemoScrollViewOffsetView` within `NavigationView{}`, I get the following warning message in the debug window when running the simulator: `Bound preference ViewOffsetKey tried to update multiple times per frame.` What is causing the warning/error and how can it be resolved? – Vee Sep 24 '20 at 01:09
  • I'm new to SwiftUI - this solution worked for me, but can you explain a little more about the `.background` modifier on the `VStack`? Is this just for the purposes of getting the scroll position? Thanks. – Gareth Lewis Mar 21 '21 at 10:16
  • @Asperi why have you declared offset var if it is not used? – Duck May 13 '21 at 13:10
  • @Vee I have the same problem. Has it been solved? – Simon Jun 30 '21 at 04:47
  • @Simon - no I never figured out the root cause of the warning. It's been a while but in my case I had nested ScrollView a that caused multiple warnings and moving them around reduced/eliminated the warning. Like I said it was a while ago so I don't have any examples to readily share with you. Good luck and please post if you figure it out! – Vee Jul 01 '21 at 05:44
  • @IanWarburton you can set it with the ScrollViewReader view, https://developer.apple.com/documentation/swiftui/scrollviewreader – Evan Aug 24 '21 at 23:44
  • @Asperi, If we want to identify bottom end of scroll then how we can get it ? – Sapana Ranipa Nov 22 '21 at 01:12
3

I found a version without using PreferenceKey. The idea is simple - by returning Color from GeometryReader, we can set scrollOffset directly inside background modifier.

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 -> Color in
                DispatchQueue.main.async {
                    scrollOffset = -proxy.frame(in: .named("scroll")).origin.y
                }
                return Color.clear
            })
        }.coordinateSpace(name: "scroll")
    }
}
Mofawaw
  • 4,544
  • 4
  • 26
  • 58
0

I had a similar need but with List instead of ScrollView, and wanted to know wether items in the lists are visible or not (List preloads views not yet visible, so onAppear()/onDisappear() are not suitable).

After a bit of "beautification" I ended up with this usage:

struct ContentView: View {
    var body: some View {
        GeometryReader { geometry in
            List(0..<100) { i in
                Text("Item \(i)")
                    .onItemFrameChanged(listGeometry: geometry) { (frame: CGRect?) in
                        print("rect of item \(i): \(String(describing: frame)))")
                    }
            }
            .trackListFrame()
        }
    }
}

which is backed by this Swift package: https://github.com/Ceylo/ListItemTracking

Ceylo
  • 304
  • 2
  • 11