I am trying to create a custom scroll view which can notify the parent view if the user has scrolled to the end.
This is how I implement this:
struct CustomVerticalScrollView<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
ScrollView {
content
Color
.clear
.frame(width: 0, height: 0)
.modifier(ViewAppearsOnScreen())
.onPreferenceChange(IsOnScreenKey.self) { value in
if value == true {
print("Has reached end!")
}
}
}
.background(.green)
}
}
struct ViewAppearsOnScreen: ViewModifier {
func body(content: Content) -> some View {
GeometryReader { geometry in
// Check if this view's frame intersects with the visible screen bounds
content
.preference(
key: IsOnScreenKey.self,
value: isViewVisible(geometry: geometry)
)
}
}
// Determines if the view is visible within the screen's bounds
private func isViewVisible(geometry: GeometryProxy) -> Bool {
let frame = geometry.frame(in: .global) // Get the frame in global space
let screenBounds = UIScreen.main.bounds // Screen boundaries
return screenBounds.intersects(frame) // Check if it intersects with screen bounds
}
}
The code used here to detect the end of the scroll view was taken from this answer
And then this is how I use it:
struct ContentView: View {
var body: some View {
CustomVerticalScrollView{
VStack(spacing: .zero) {
Color
.blue
.frame(maxWidth: .infinity)
.frame(height: 1000)
}
}
}
}
The functionality works well, however, This gives me the following results with some extra spacing:
I though setting the height to 0 and using the Geometry reader would not impact the spacing.
Just wondering how can I eliminate all this extra spacing so that I can have this additional view in there.
GeometryReader
in this case will resize itself to its ideal height, which happens to be a small, non-zero value. You can move frame(width: 0, height: 0)
to after onPreferenceChange
to fix this.
Every SwiftUI view has its own "preferred spacing" on all four edges, and this spacing can differ depending on the type of the other view. For example, the preferred spacing between a Text
and an Image
is a bit larger than the preferred spacing between two Text
s. The default layout behaviour respects this spacing preference. When implementing your own Layout
s, you can read this preferred spacing using LayoutSubview.spacing
.
Using a VStack
with an explicitly spacing:
parameter makes the VStack
layout not respect the spacing preference.
ScrollView {
VStack(spacing: 0) {
content
Color
.clear
.modifier(ViewAppearsOnScreen())
.onPreferenceChange(IsOnScreenKey.self) { ... }
.frame(width: 0, height: 0)
}
}