While applying this solution which shows & hides a header view as a user scrolls the content, it works fine with simple views inside the scroll view. For example, if I add 100 Text
views in the ScrollView
the effect works fine. However it isn’t working when the content view is a LazyVGrid
containing equal sized item views.
import SwiftUI
struct ContentView: View {
private let gridItemSpacing: CGFloat = 2
private let gridColumns = 5
@State private var showingHeader = true
var body: some View {
VStack {
if showingHeader {
Rectangle()
.fill(.red)
.frame(height: 80)
.transition(
.asymmetric(
insertion: .push(from: .top),
removal: .push(from: .bottom)
)
)
}
GeometryReader { outer in
let outerHeight = outer.size.height
ScrollView(.vertical) {
gridView(outer)
.background {
GeometryReader { proxy in
let contentHeight = proxy.size.height
let minY = max(
min(0, proxy.frame(in: .named("ScrollView")).minY),
outerHeight - contentHeight
)
Color.clear
.onChange(of: minY) { oldVal, newVal in
if (showingHeader && newVal < oldVal) || !showingHeader && newVal > oldVal {
showingHeader = newVal > oldVal
}
}
}
}
}
.coordinateSpace(name: "ScrollView")
}
// Prevent scrolling into the safe area
.padding(.top, 1)
}
.background(.black)
.animation(.easeInOut, value: showingHeader)
}
private func gridView(_ geo: GeometryProxy) -> some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: gridItemSpacing),
count: gridColumns), spacing: gridItemSpacing) {
ForEach(0..<300) { index in
let itemSize = (geo.size.width - CGFloat(gridColumns - 1) * gridItemSpacing) / CGFloat(gridColumns)
Rectangle()
.fill(.yellow)
.frame(width: itemSize, height: itemSize)
}
}
}
}
I’m using the outer GeometryReader
to calculate the size of the grid items. When scrolling, the header moves upwards and sort of bounces with a disabled sort of look and comes back to its resting position once scrolling ends. I suspect it has something to do with the LazyVGrid
layout. Do I need to specify a frame for the Grid since it is Lazy?