Created
June 27, 2021 12:46
-
-
Save novinfard/35350a83fb016696de2b7037f1dd9d21 to your computer and use it in GitHub Desktop.
[Trackable ScrollView in SwiftUI]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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