'SwiftUI ForEach nested in ScrollView breaks onPreferenceChange [duplicate]

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



Solution 1:[1]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Steve M