I am trying to ascertain which GridItem would effectively be at position x:0, y:0.
To achieve this I am simply trying to use a preferenceKey and GeometryReader.
I am adding an .overlay to my GridItems and on the GridItem at gridItemIndex 0 adding the GeometryReader around a Color.clear. My expected logic is to track the Y position of that GridItem. Then by dividing that Y offset by the height of each GridItem I will get which item is currently at the top.
I have this working to a point. Once the GridItem at index "gridItemIndex" position 0 is above a certain offset it is no longer read and the y position rests to 0.0. My assumption for this is due to the view being reused?
Currently I am not getting the reading above 40 but I need to get until the bottom of the LazyVGrid appears.
Here is my code
struct DetectScrollPosition: View {
let gridRowLayout = Array(repeating: GridItem(spacing: 0), count: 7)
@State private var scrollPosition: Int = 0
var body: some View {
NavigationView {
ScrollView (.vertical){
LazyVGrid(columns: gridRowLayout, spacing: 0){
ForEach(0..<1092, id: \.self) { gridItemIndex in
Text("\(abs(gridItemIndex / 7))")
.overlay {
if gridItemIndex == 0 {
GeometryReader { geometryProxy in
Color.clear
.updateViewsYPosition(geometryProxy)
}
}
}
}
}
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
self.scrollPosition = abs(Int(value))
}
}
.coordinateSpace(.named("scroll"))
.navigationTitle("The Top Row is: \(scrollPosition)")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
}
}
extension View {
func updateViewsYPosition(_ geometryProxy: GeometryProxy) -> some View {
let offset = geometryProxy.frame(in: .named("scroll")).origin.y / geometryProxy.frame(in: .named("scroll")).height
return self.preference(key: ScrollOffsetPreferenceKey.self, value: offset)
}
}

Indeed, the views are being reused. You should add a view that sets the preference for every row of the grid.
Now we need to implement the
reducemethod in the preference key, because there will be multiple sibling views all setting their own preference. The idea is, after reducing everything, the end result will indicate the view that is on the top left.Therefore, we need to store both the frame of the view (for reducing) and the index of the view (so that we can update
scrollPosition). We will use a type like this for the preference key:This is why we also pass in
gridItemIndextoupdateViewsYPositionin theoverlay.The actual preference key would be implemented like this:
reduceis implemented so that the preference is always the view with a y position that is closest to 0. You can change this criteria to whatever you want.In
updateViewsYPosition, you should get the frame in thescrollViewcoordinate space.Full code: