0

I am trying to get the current scroll value of a ScrollView to be used elsewhere in other views.

It appears the only way to do it is using GeometryReader and store its value in a PreferenceKey to be then read by onPreferenceChange so that we do not change the state of the view during rendering (which is forbidden).

Everything seems to work fine until I embed a ForEach in the scrollview instead of some static views. Then the onPreferenceChange stops receiving events.

Here is a NON working example:


struct ScrollViewPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct ContentView: View {

    var body: some View {

        ScrollView {

            GeometryReader { proxy in
                Color.clear.preference(key: ScrollViewPreferenceKey.self,
                                       value: proxy.frame(in: .named("scrollview")).minY)
            }

            VStack(spacing: 20) {
                ForEach(0..<27) { item in // this is causing the issue
                    Text("Item \(item)")
                }
            } //end vstack
        } //end scrollview
        .coordinateSpace(name: "scrollview")
        .onPreferenceChange(ScrollViewPreferenceKey.self, perform: { value in
            let _ = print(value)
        })

    }//endbody
} //endview

As result nothing is printed

Replacing the ForEach { } with hardcoded views works just fine:

                Group {
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                }
                Group {
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                }
                Group {
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                    Text("Item x")
                }

Results in printing the correct values

0.0 -11.666666666666664 -49.333333333333336 -104.33333333333334 -174.33333333333331 -252.66666666666666 -315.0 ...

I figure there must be some issue with the scrollview and dynamic content but what and how to solve it?

The issue happens even if the ForEach is embedded several layers of View deep in the herarchy or in other files.

Is this just another SwiftUI bug or there is something i am not grasping (I am new to the framework)?

jalone
  • 1,953
  • 4
  • 27
  • 46

1 Answers1

2

Simply

struct ContentView: View {

    var body: some View {

        ScrollView {

            GeometryReader { proxy in
                Color.clear.preference(key: ScrollViewPreferenceKey.self,
                                       value: proxy.frame(in: .named("scrollview")).minY)
            }

            ForEach(0..<27, id: \.self) { item in // this is causing the issue
                Text("Item \(item)")
                    .padding()
            }
        } //end scrollview
        .coordinateSpace(name: "scrollview")
        .onPreferenceChange(ScrollViewPreferenceKey.self, perform: { value in
            let _ = print(value)
        })

    }//endbody
} //endview

worked. The VStack was causing the problem.

Steve M
  • 9,296
  • 11
  • 49
  • 98
  • This is so weird, and sadly i need the VStavk, thisvis a simplified problem. :/ also do you know the reason? Thank you – jalone May 01 '22 at 09:00
  • 1
    You can also make it work by putting the GeometryReader below the VStack. Not sure if those are the numbers you're expecting tho. Not really sure why it doesn't work as you originally had it, sorry. – Steve M May 01 '22 at 13:12
  • Yes! it works also putting the `onPreferenceChange` directly on the `GeometryReader`. One day we will know if a bug or a feature i guess. Thanks – jalone May 01 '22 at 22:06