Skip to content

Instantly share code, notes, and snippets.

@LucasAbijmil
Created February 5, 2023 20:31
Show Gist options
  • Save LucasAbijmil/3cf147c750b806f4a56eea630d648b2b to your computer and use it in GitHub Desktop.
Save LucasAbijmil/3cf147c750b806f4a56eea630d648b2b to your computer and use it in GitHub Desktop.
Detect if the user scrolls in SwiftUI
struct ScrollViewDelegate<Content: View>: View {
@State private var verticalScrollTimer: Timer?
@State private var horizontalScrollTimer: Timer?
@ViewBuilder private let content: Content
private let axes: Axis.Set
private let showsIndicators: Bool
private let onVerticalScroll: ((_ isScrolling: Bool) -> Void)?
private let onHorizontalScroll: ((_ isScrolling: Bool) -> Void)?
private let coordinateSpace = "scrollView"
private let scrollDetectorInterval = 0.25
init(_ axes: Axis.Set = .vertical,
showsIndicators: Bool = true,
@ViewBuilder content: () -> Content,
onVerticalScroll: ((_ isScrolling: Bool) -> Void)? = nil,
onHorizontalScroll: ((_ isScrolling: Bool) -> Void)? = nil) {
self.axes = axes
self.showsIndicators = showsIndicators
self.content = content()
self.onVerticalScroll = axes.contains(.vertical) ? onVerticalScroll : nil
self.onHorizontalScroll = axes.contains(.horizontal) ? onHorizontalScroll : nil
}
var body: some View {
ScrollView(axes, showsIndicators: showsIndicators) {
content
.background {
GeometryReader { proxy in
Color.clear.preference(key: YOffsetKey.self, value: -proxy.frame(in: .named(coordinateSpace)).minY)
Color.clear.preference(key: XOffsetKey.self, value: -proxy.frame(in: .named(coordinateSpace)).minX)
}
}
}
.coordinateSpace(name: coordinateSpace)
.onPreferenceChange(YOffsetKey.self) { value in
guard axes.contains(.vertical) else { return }
verticalScrollTimer?.invalidate()
verticalScrollTimer = Timer.scheduledTimer(withTimeInterval: scrollDetectorInterval, repeats: false) { _ in
onVerticalScroll?(false)
}
onVerticalScroll?(value > 0)
}
.onPreferenceChange(XOffsetKey.self) { value in
guard axes.contains(.horizontal) else { return }
horizontalScrollTimer?.invalidate()
horizontalScrollTimer = Timer.scheduledTimer(withTimeInterval: scrollDetectorInterval, repeats: false) { _ in
onHorizontalScroll?(false)
}
onHorizontalScroll?(value > 0)
}
}
}
private struct YOffsetKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
private struct XOffsetKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment