I've been fighting with GeometryReader
and .onChange(of: geo.frame(in: .global).minY)
for a long time trying to get this to work with no great success. Consider the following:
struct TestScreen: View {
var body: some View {
NavigationStack {
ScrollView {
VStack {
Text("View A")
.frame(height: 50)
.frame(maxWidth: .infinity)
.background(.green)
Text("View B - Sticky")
.frame(height: 50)
.frame(maxWidth: .infinity)
.background(.blue)
ForEach(0..<15) { i in
Text("View \(i)")
.frame(height: 50)
.frame(maxWidth: .infinity)
.background(Color.red)
}
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.clear, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .principal) {
Text("Testing")
.foregroundColor(.purple)
}
}
}
}
}
The goal is to make View B stick to the top (just under the navigation bar) when you scroll the view upwards, and of course take its normal place in the scrollview when you scroll back down. I know you can do sticky headers with List
and Sections
but that doesn't suit my needs because note that View B (the sticky view) isn't necessarily the first item in the scrollview.
Also note that View B must stay on top of all other content in the VStack so that the other content scrolls beneath it.
Try using a LazyVStack
with sections and pinned headers:
ForEach
as its main content and View B as its header.ScrollView {
LazyVStack(pinnedViews: .sectionHeaders) {
Section {
Text("View A")
// ... modifiers as before
}
Section {
ForEach(0..<15) { i in
// ... content as before
}
} header: {
Text("View B - Sticky")
// ... modifiers as before
}
}
}