Skip to content

Instantly share code, notes, and snippets.

@novinfard
Created June 27, 2021 12:46
Show Gist options
  • Save novinfard/35350a83fb016696de2b7037f1dd9d21 to your computer and use it in GitHub Desktop.
Save novinfard/35350a83fb016696de2b7037f1dd9d21 to your computer and use it in GitHub Desktop.
[Trackable ScrollView in SwiftUI]
//
// TrackableScrollView.swift
// Lions
//
// Created by Soheil Novinfard on 22/04/2021.
// Copyright © 2021 David Rodriguez Luque. All rights reserved.
//
import SwiftUI
// Highly Inspired from https://medium.com/@maxnatchanon/swiftui-how-to-get-content-offset-from-scrollview-5ce1f84603ec
struct TrackableScrollView<Content>: View where Content: View {
let axes: Axis.Set
let showIndicators: Bool
@Binding var contentOffset: CGFloat
let content: Content
/// Creates a new instance that’s scrollable in the direction of the given axis and can show indicators while scrolling.
/// - Parameters:
/// - axes: The scrollable axes of the scroll view.
/// - showIndicators: A value that indicates whether the scroll view displays the scrollable component of the content offset, in a way that’s suitable for the platform.
/// - contentOffset: A value that indicates offset of content.
/// - content: The scroll view’s content.
init(_ axes: Axis.Set = .vertical, showIndicators: Bool = true, contentOffset: Binding<CGFloat>, @ViewBuilder content: () -> Content) {
self.axes = axes
self.showIndicators = showIndicators
_contentOffset = contentOffset
self.content = content()
}
var body: some View {
GeometryReader { outsideProxy in
ScrollView(self.axes, showsIndicators: self.showIndicators) {
ZStack(alignment: self.axes == .vertical ? .top : .leading) {
GeometryReader { insideProxy in
Color.clear
.preference(key: ScrollOffsetPreferenceKey.self, value: [self.calculateContentOffset(fromOutsideProxy: outsideProxy, insideProxy: insideProxy)])
}
VStack {
self.content
}
}
}
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
self.contentOffset = value[0]
}
}
}
private func calculateContentOffset(fromOutsideProxy outsideProxy: GeometryProxy, insideProxy: GeometryProxy) -> CGFloat {
if axes == .vertical {
return outsideProxy.frame(in: .global).minY - insideProxy.frame(in: .global).minY
} else {
return outsideProxy.frame(in: .global).minX - insideProxy.frame(in: .global).minX
}
}
}
struct ScrollOffsetPreferenceKey: PreferenceKey {
typealias Value = [CGFloat]
static var defaultValue: [CGFloat] = [0]
static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
value.append(contentsOf: nextValue())
}
}
// Usage
struct MyView: View {
@Binding var scrollViewContentOffset: CGFloat
var body: some View {
GeometryReader { geo in
TrackableScrollView(.vertical, showIndicators: false, contentOffset: $scrollViewContentOffset) {
// ScrollView Content
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment