updates
This commit is contained in:
@@ -1,7 +1,4 @@
|
||||
import SwiftUI
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
struct CategoryHeaderOffsetPreferenceKey: PreferenceKey {
|
||||
static let defaultValue: [String: CGFloat] = [:]
|
||||
@@ -11,125 +8,41 @@ struct CategoryHeaderOffsetPreferenceKey: PreferenceKey {
|
||||
}
|
||||
}
|
||||
|
||||
enum StoreDetailScrollCoordinateSpace {
|
||||
static let name = "store-detail-scroll"
|
||||
}
|
||||
|
||||
struct ScrollOffsetPreferenceKey: PreferenceKey {
|
||||
static let defaultValue: CGFloat = 0
|
||||
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
struct ScrollOffsetReader: View {
|
||||
@Binding var offsetY: CGFloat
|
||||
|
||||
var body: some View {
|
||||
#if canImport(UIKit)
|
||||
ScrollOffsetReaderRepresentable(offsetY: $offsetY)
|
||||
#else
|
||||
Color.clear
|
||||
#endif
|
||||
.frame(height: 0)
|
||||
.background(
|
||||
GeometryReader { geometry in
|
||||
Color.clear.preference(
|
||||
key: ScrollOffsetPreferenceKey.self,
|
||||
value: geometry.frame(in: .named(StoreDetailScrollCoordinateSpace.name)).minY
|
||||
)
|
||||
}
|
||||
)
|
||||
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { minY in
|
||||
let normalizedOffset = max(0, -minY)
|
||||
if abs(offsetY - normalizedOffset) > 0.5 {
|
||||
offsetY = normalizedOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
struct ScrollOffsetReaderRepresentable: UIViewRepresentable {
|
||||
@Binding var offsetY: CGFloat
|
||||
|
||||
func makeUIView(context: Context) -> OffsetProbeView {
|
||||
let view = OffsetProbeView()
|
||||
view.onOffsetChanged = { value in
|
||||
if offsetY != value {
|
||||
offsetY = value
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: OffsetProbeView, context: Context) {
|
||||
uiView.onOffsetChanged = { value in
|
||||
if offsetY != value {
|
||||
offsetY = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class OffsetProbeView: UIView {
|
||||
var onOffsetChanged: ((CGFloat) -> Void)?
|
||||
private var observation: NSKeyValueObservation?
|
||||
private weak var observedScrollView: UIScrollView?
|
||||
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
attachIfNeeded()
|
||||
}
|
||||
|
||||
override func didMoveToSuperview() {
|
||||
super.didMoveToSuperview()
|
||||
attachIfNeeded()
|
||||
}
|
||||
|
||||
private func attachIfNeeded() {
|
||||
guard observation == nil else { return }
|
||||
guard observedScrollView == nil else { return }
|
||||
|
||||
if let scrollView = enclosingScrollView() ?? findScrollViewInWindow() {
|
||||
observe(scrollView)
|
||||
return
|
||||
}
|
||||
|
||||
retryAttach()
|
||||
}
|
||||
|
||||
private func observe(_ scrollView: UIScrollView) {
|
||||
observedScrollView = scrollView
|
||||
observation = scrollView.observe(\.contentOffset, options: [.new, .initial]) { [weak self, weak scrollView] _, change in
|
||||
guard let self, let scrollView, let y = change.newValue?.y else { return }
|
||||
let adjusted = max(0, y + scrollView.adjustedContentInset.top)
|
||||
DispatchQueue.main.async {
|
||||
self.onOffsetChanged?(adjusted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func retryAttach() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
|
||||
self?.attachIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
private func enclosingScrollView() -> UIScrollView? {
|
||||
var current: UIView? = self
|
||||
while let view = current {
|
||||
if let scrollView = view as? UIScrollView {
|
||||
return scrollView
|
||||
}
|
||||
current = view.superview
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func findScrollViewInWindow() -> UIScrollView? {
|
||||
guard let window else { return nil }
|
||||
let targetPoint = convert(CGPoint(x: bounds.midX, y: bounds.midY), to: window)
|
||||
return findScrollView(in: window, containing: targetPoint)
|
||||
}
|
||||
|
||||
private func findScrollView(in root: UIView, containing point: CGPoint) -> UIScrollView? {
|
||||
for subview in root.subviews.reversed() {
|
||||
if let match = findScrollView(in: subview, containing: point) {
|
||||
return match
|
||||
}
|
||||
}
|
||||
|
||||
if let scrollView = root as? UIScrollView {
|
||||
let rectInWindow = scrollView.convert(scrollView.bounds, to: window)
|
||||
if rectInWindow.contains(point) {
|
||||
return scrollView
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
observation?.invalidate()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
struct AsyncStoreImage: View {
|
||||
let imageURL: String?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user