onmyway133 / blog

🍁 What you don't know is what you haven't learned
https://onmyway133.com/
MIT License
669 stars 33 forks source link

How to track contentSize and scroll offset for ScrollView in SwiftUI #829

Open onmyway133 opened 2 years ago

onmyway133 commented 2 years ago

Use PreferenceKey with a custom coordinateSpace to make our own TrackableScrollView. Note that the offset here is in reversed to contentOffset in normal UIScrollView

import SwiftUI

struct TrackableScrollView<Content: View>: View {
    @ViewBuilder let content: (ScrollViewProxy) -> Content

    let onContentSizeChange: (CGSize) -> Void
    let onOffsetChange: (CGPoint) -> Void

    var body: some View {
        ScrollViewReader { reader in
            ScrollView(showsIndicators: false) {
                GeometryReader { geo in
                    Color.clear.preference(
                        key: ScrollOffsetKey.self,
                        value: geo.frame(in: .named("scrollView")).origin
                    )
                    .frame(width: 0, height: 0)
                }
                content(reader)
                    .background(
                        GeometryReader { geo -> Color in
                            DispatchQueue.main.async {
                                onContentSizeChange(geo.size)
                            }
                            return Color.clear
                        }
                    )
            }
            .coordinateSpace(name: "scrollView")
            .onPreferenceChange(ScrollOffsetKey.self) { offset in
                onOffsetChange(offset)
            }
        }
    }
}

private struct ScrollOffsetKey: PreferenceKey {
    typealias Value = CGPoint
    static var defaultValue = CGPoint.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {}
}

Read more