Get the Current Scroll Position of a Swiftui Scrollview

Get the current scroll position of a SwiftUI ScrollView

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

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

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

Set scroll position from a different view in scrollview

What a horrible MRE!! This is what you could mean and how it works:

One. you have to put .id()s on your ScrollView, otherwise scollTo doesn't know where to go.

Two. There have to be enough entries in the ScrollView. If all are visible, the scrollTo won't do anything.

class EnvironmentVariables: ObservableObject {
@Published var currentIndexforPosts: Int = 0
}


struct ContentView: View {

@StateObject var environmentVariables = EnvironmentVariables()

var body: some View {
VStack {
ButtonView()
ScrollView1()
}
.environmentObject(environmentVariables)
}
}


struct ScrollView1: View {

@EnvironmentObject var environmentVariables: EnvironmentVariables

var body: some View {

ScrollView {
ScrollViewReader { value in
LazyVStack {
ForEach(0..<100) { i in
Text ("Text \(i)").id(i)
.font(.title)
}
}

.onChange(of: environmentVariables.currentIndexforPosts) { _ in
if environmentVariables.currentIndexforPosts >= 0 {
withAnimation {
value.scrollTo(environmentVariables.currentIndexforPosts, anchor: .top)
}
}
}
}
}
}
}

struct ButtonView: View {

@EnvironmentObject var environmentVariables: EnvironmentVariables

var body: some View {

Button("Go to next") {
environmentVariables.currentIndexforPosts += 10
}
.padding()

Button("Go to previous") {
environmentVariables.currentIndexforPosts -= 10
}
}
}


Related Topics



Leave a reply



Submit