Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save ScrollViews position and scroll back to it later (offset to position)

I have found a way to save a ScrollViews offset with a GeometryReader and a PreferenceKey.

SwiftUI | Get current scroll position from ScrollView

And the ScrollViewReader has a method scrollTo to scroll to a set position.

scrollTo

The problem is, that the first one saves an offset while the second method expects a position (or an id, which is similar to the position in my case). How can I convert the offset to a position/id or is there any other way to save and load a ScrollViews position?

Here is the code I have now but it does not scroll the way I want:

ScrollView {
    ScrollViewReader { scrollView in
        LazyVGrid(columns: columns, spacing: 0) {
            ForEach(childObjects, id: \.id) { obj in
                CustomView(obj: obj).id(obj.id)
            }
        }
        .onChange(of: scrollTarget) { target in
            if let target = target {
                scrollTarget = nil
                scrollView.scrollTo(target, anchor: .center)
            }
        }
        .background(GeometryReader {
            Color.clear.preference(key: ViewOffsetKey.self,
                value: -$0.frame(in: .named("scroll")).origin.y)
        })
        .onPreferenceChange(ViewOffsetKey.self) { // save $0 }
    }
}.coordinateSpace(name: "scroll")

And in the onAppear of the View I want to set scrollTarget to the saved position. But it scrolls anywhere but not to the position I want.

I thought about dividing the offset by the size of one item but is that really the way to go? It does not sound very good.

like image 519
L3n95 Avatar asked Nov 15 '25 23:11

L3n95


1 Answers

You don't need actually offset in this scenario, just store id of currently visible view (you can use any appropriate algorithm for your data of how to detect it) and then scroll to view with that id.

Here is a simplified demo of possible approach. Tested with Xcode 12.1/iOS 14.1

demo

struct TestScrollBackView: View {
    @State private var stored: Int = 0
    @State private var current: [Int] = []
    
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                HStack {
                    Button("Store") {
                        // hard code is just for demo !!!
                        stored = current.sorted()[1] // 1st is out of screen by LazyVStack
                        print("!! stored \(stored)")
                    }
                    Button("Restore") {
                        proxy.scrollTo(stored, anchor: .top)
                        print("[x] restored \(stored)")
                    }
                }
                Divider()
                ScrollView {
                    LazyVStack {
                        ForEach(0..<1000, id: \.self) { obj in
                            Text("Item: \(obj)")
                                .onAppear {
                                    print(">> added \(obj)")
                                    current.append(obj)
                                }
                                .onDisappear {
                                    current.removeAll { $0 == obj }
                                    print("<< removed \(obj)")
                                }.id(obj)
                        }
                    }
                }
            }
        }
    }
}
like image 74
Asperi Avatar answered Nov 18 '25 15:11

Asperi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!