Added offset publisher. See README on how to use it.
This commit is contained in:
parent
c49d732aae
commit
3ecd26174d
71
README.md
71
README.md
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue