Added offset publisher. See README on how to use it.

This commit is contained in:
Casper Zandbergen 2020-12-07 13:10:08 +01:00
parent c49d732aae
commit 3ecd26174d
2 changed files with 104 additions and 57 deletions

View File

@ -52,44 +52,53 @@ ScrollView { proxy in
Everything put together in an example
```swift
struct ScrollViewProxySimpleExample: View {
struct ScrollViewProxyExample: View {
@State var randomInt = Int.random(in: 0..<200)
@State var proxy: ScrollViewProxy? = nil
@State var offset: CGPoint = .zero
var body: some View {
VStack {
ScrollView { proxy in
ForEach(0..<200) { index in
VStack {
Text("\(index)").font(.title)
Spacer()
// GeometryReader for safeAreaInsets on Sticky View
GeometryReader { geometry in
VStack {
ScrollView { proxy in
Text("Sticky View")
.background(Color.white)
.onReceive(proxy.offset) { self.offset = $0 }
.offset(x: offset.x, y: offset.y + geometry.safeAreaInsets.top)
.zIndex(1)
VStack(spacing: 20) {
ForEach(0..<200) { index in
HStack {
Spacer()
Text("Item: \(index)").font(.title)
Spacer()
}.scrollId(index)
}
}
.zIndex(0)
.onAppear {
self.proxy = proxy
}
.padding()
.scrollId(index)
}.onAppear {
self.proxy = proxy
}
}
HStack {
Button(action: {
self.proxy?.scrollTo(self.randomInt, alignment: .center)
self.randomInt = Int.random(in: 0..<200)
}, label: {
Text("Go to \(self.randomInt)")
})
Spacer()
Button(action: { self.proxy?.scrollTo(.top) }, label: {
Text("Top")
})
Spacer()
Button(action: { self.proxy?.scrollTo(.center) }, label: {
Text("Center")
})
Spacer()
Button(action: { self.proxy?.scrollTo(.bottom) }, label: {
Text("Bottom")
})
HStack {
Button(action: {
self.proxy?.scrollTo(self.randomInt, alignment: .center)
self.randomInt = Int.random(in: 0..<200)
}, label: {
Text("Go to \(self.randomInt)")
})
Spacer()
Button(action: { self.proxy?.scrollTo(.bottom) }, label: {
Text("Bottom")
})
Spacer()
Button(action: { self.proxy?.scrollTo(.center) }, label: {
Text("Center")
})
}.padding()
}
}
}

View File

@ -2,12 +2,25 @@
// https://twitter.com/amzdme
import Introspect
import SwiftUI
import Combine
// MARK: Fix for name collision when using SwiftUI 2.0
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias AmzdScrollViewProxy = ScrollViewProxy
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias AmzdScrollViewReader = ScrollViewReader
// MARK: Platform specifics
#if os(macOS)
public typealias PlatformScrollView = NSScrollView
var visibleSizePath = \PlatformScrollView.documentVisibleRect.size
var adjustedContentInsetPath = \PlatformScrollView.contentInsets
var contentSizePath = \PlatformScrollView.documentView!.frame.size
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension NSScrollView {
func scrollRectToVisible(_ rect: CGRect, animated: Bool) {
if animated {
@ -19,15 +32,50 @@ extension NSScrollView {
contentView.scrollToVisible(rect)
}
}
var offsetPublisher: OffsetPublisher {
publisher(for: \.contentView.bounds.origin).eraseToAnyPublisher()
}
}
extension NSEdgeInsets {
/// top + bottom
var vertical: CGFloat {
return top + bottom
}
/// left + right
var horizontal: CGFloat {
return left + right
}
}
#elseif os(iOS) || os(tvOS)
public typealias PlatformScrollView = UIScrollView
@available(iOS 12.0, *)
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
var visibleSizePath = \PlatformScrollView.visibleSize
var adjustedContentInsetPath = \PlatformScrollView.adjustedContentInset
var contentSizePath = \PlatformScrollView.contentSize
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension UIScrollView {
var offsetPublisher: OffsetPublisher {
publisher(for: \.contentOffset).eraseToAnyPublisher()
}
}
extension UIEdgeInsets {
/// top + bottom
var vertical: CGFloat {
return top + bottom
}
/// left + right
var horizontal: CGFloat {
return left + right
}
}
#endif
// MARK: Helper extensions
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ScrollView {
/// Creates a ScrollView with a ScrollViewReader
@ -49,6 +97,8 @@ extension View {
public func id<ID: Hashable>(_ id: ID, scrollView: ScrollViewProxy) -> some View { self }
}
// MARK: Preferences
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
struct ScrollViewProxyPreferenceData: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
@ -80,6 +130,8 @@ struct ScrollViewProxyPreferenceModifier: ViewModifier {
}
}
// MARK: ScrollViewReader
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ScrollViewReader<Content: View>: View {
private var content: (ScrollViewProxy) -> Content
@ -102,10 +154,18 @@ public struct ScrollViewReader<Content: View>: View {
// seems this will not be called due to ScrollView/Preference issues
// https://stackoverflow.com/a/61765994/3019595
}
.introspectScrollView { self.proxy.coordinator.scrollView = $0 }
.introspectScrollView {
self.proxy.coordinator.scrollView = $0
self.proxy.offset = $0.offsetPublisher
}
}
}
// MARK: ScrollViewProxy
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public typealias OffsetPublisher = AnyPublisher<CGPoint, Never>
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct ScrollViewProxy {
fileprivate class Coordinator {
@ -116,6 +176,8 @@ public struct ScrollViewProxy {
fileprivate var space: UUID = UUID()
fileprivate init() {}
public var offset: OffsetPublisher = Just(.zero).eraseToAnyPublisher()
/// Scrolls to an edge or corner
public func scrollTo(_ alignment: Alignment, animated: Bool = true) {
@ -187,27 +249,3 @@ public struct ScrollViewProxy {
}
}
#if os(macOS)
extension NSEdgeInsets {
/// top + bottom
var vertical: CGFloat {
return top + bottom
}
/// left + right
var horizontal: CGFloat {
return left + right
}
}
#elseif os(iOS) || os(tvOS)
extension UIEdgeInsets {
/// top + bottom
var vertical: CGFloat {
return top + bottom
}
/// left + right
var horizontal: CGFloat {
return left + right
}
}
#endif