I have a SwiftUI ScrollView
that contains a LazyVStack
of tappable elements. When I tap on an element I wish to display a small popover at a position relative to that of the tapped element, regardless of the ScrollView
offset. The popover position should be consistent between targets and not simply based on the screen location.
The actual design involves blurring the entire screen on the tap and presenting a new layer with a copy of the tapped element and the popover on a new, unblurred, layer. This means that an overlay-based approach won't work in this case.
I have a solution but it feels heavyweight and wonder if I've missed a native version.
My ScrollView
is wrapped into an OffsetReporting version (adapted from the version presented here), so I know its current offset whenever the user scrolls. My elements have a GeometryReader
overlay that gets their position. When elements are created I can combine the ScrollView
offset and the element position and store the two as the absolute position of the element within the ScrollView
. When I later need to know the position on screen I can take the current ScrollView
offset and adjust the stored element position accordingly.
The broad strokes are presented below. Is there a simpler or more native way of achieving the same?
// struct and other wrapping code elided
OffsetReportingScrollView(
.vertical,
showsIndicators: false,
offsetChanged: { offset in
// Store the ScrollView's offset.
})
{
ZStack {
LazyVStack {
ForEach(0...100, id: \.self) { (i: Int) in
HStack {
Button {
// Enable the popover at the correct position via a property.
} label: {
Text("Item \(i)")
}
.buttonStyle(.plain)
}
// Capture the element's position on creation
.overlay {
GeometryReader { geometry in
Rectangle()
.foregroundColor(Color.clear)
.onAppear {
// Store the element's position w.r.t. the ScrollView's current offset.
}
}
}
}
}
}
}