-
-
Save khanlou/112cbb13ee2c776aa343bfc204f78259 to your computer and use it in GitHub Desktop.
struct ContentView: View { | |
@State var scrollOffset: CGPoint = .zero | |
var body: some View { | |
ObservableScrollView { | |
Text("Hello, world!") | |
.foregroundColor(self.scrollOffset.y == 0 ? .blue : .red) | |
} | |
.onScroll { self.scrollOffset = $0 } | |
} | |
} |
struct ScrollOffsetPreferenceKey: PreferenceKey { | |
typealias Value = CGPoint | |
static var defaultValue = CGPoint.zero | |
static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { | |
value = nextValue() | |
} | |
} | |
struct ObservableScrollView<Content> : View where Content : View { | |
var content: Content | |
var axes: Axis.Set | |
var showsIndicators: Bool | |
init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, @ViewBuilder content: () -> Content) { | |
self.content = content() | |
self.axes = axes | |
self.showsIndicators = showsIndicators | |
} | |
var body: some View { | |
GeometryReader { outerGeometry in | |
ScrollView(self.axes, showsIndicators: self.showsIndicators) { | |
ZStack(alignment: self.axes == .vertical ? .top : .leading) { | |
GeometryReader { innerGeometry in | |
Color.clear | |
.preference(key: ScrollOffsetPreferenceKey.self, value: CGPoint(x: (outerGeometry.frame(in: .global).minX - innerGeometry.frame(in: .global).minX), y: (outerGeometry.frame(in: .global).minY - innerGeometry.frame(in: .global).minY))) | |
} | |
VStack { | |
self.content | |
} | |
} | |
} | |
} | |
} | |
} | |
extension ObservableScrollView { | |
func onScroll(_ onScroll: @escaping (CGPoint) -> ()) -> some View { | |
self.onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: onScroll) | |
} | |
} |
Fixed a bug, with inspiration from https://medium.com/@maxnatchanon/swiftui-how-to-get-content-offset-from-scrollview-5ce1f84603ec who uses a similar technique
Thanks for sharing!!!
I did a quick test but found that any layout change causing the infinite loop. for example, offset ObservableScrollView Y position or image height based on offset. is there a way to fix the call back only for content scroll not the frame change?
struct ContentView: View {
@State var scrollOffset: CGPoint = .zero
var body: some View {
VStack {
//1
Image("logo_stjc")
.resizable()
.aspectRatio(contentMode: .fill)
.clipped()
.frame(height: 400)
//2
VStack {
Image(systemName: "trash")
.frame(width: 50.0, height: 50.0)
}
.frame(width: 200, height: 100)
//3
ObservableScrollView {
VStack(spacing: 20) {
HStack(spacing: 20){
//some vie
}
}
}.onScroll { self.scrollOffset = $0 }
}
}
}
@palanishankar07 to be honest, i haven’t been able to get this code to work reliably, so I’ve kind of abandoned it. I’m not getting the body var get called when the user scrolls. Let me know if you have better luck with it!
If anyone has been looking for a solution I found this really convenient to use
It has all kinds of customisation available, like making the scrollview a grid scrollview , tracking scroll offset putting a header, adjusting padding etc
It is a brilliant solution
Wrong implementation of reduce func. Closure will not called.
thank you for sharing, will give it a try 👍