|
import SwiftUI |
|
@_spi(Advanced) import SwiftUIIntrospect |
|
|
|
struct DelegateEnabledScrollView<Content: View>: View { |
|
@State private var scrollViewDelegate: DelegateEnabledScrollViewDelegate? |
|
@State private var scrollView: UIScrollView? = nil |
|
let content: (UIScrollView?) -> Content |
|
|
|
var onDidZoom: ((UIScrollView) -> Void)? |
|
var onWillBeginDragging: ((UIScrollView) -> Void)? |
|
var onDidScroll: ((UIScrollView) -> Void)? |
|
var onDidEndDecelerating: ((UIScrollView) -> Void)? |
|
var onWillBeginDecelerating: ((UIScrollView) -> Void)? |
|
var onDidScrollToTop: ((UIScrollView) -> Void)? |
|
var onShouldScrollToTop: ((UIScrollView) -> Bool)? |
|
var onDidEndScrollingAnimation: ((UIScrollView) -> Void)? |
|
var onWillBeginZooming: ((UIScrollView, UIView?) -> Void)? |
|
var onDidChangeAdjustedContentInset: ((UIScrollView) -> Void)? |
|
var onDidEndDragging:((UIScrollView, Bool) -> Void)? |
|
var onDidEndZooming:((UIScrollView, UIView?, CGFloat) -> Void)? |
|
var onWillEndDragging:((UIScrollView, CGPoint, UnsafeMutablePointer<CGPoint>) -> Void)? |
|
|
|
init(@ViewBuilder content: @escaping (UIScrollView?) -> Content, |
|
onDidZoom: ((UIScrollView) -> Void)? = nil, |
|
onWillBeginDragging: ((UIScrollView) -> Void)? = nil, |
|
onDidScroll: ((UIScrollView) -> Void)? = nil, |
|
onDidEndDecelerating: ((UIScrollView) -> Void)? = nil, |
|
onWillBeginDecelerating: ((UIScrollView) -> Void)? = nil, |
|
onDidScrollToTop: ((UIScrollView) -> Void)? = nil, |
|
onShouldScrollToTop: ((UIScrollView) -> Bool)? = nil, |
|
onDidEndScrollingAnimation: ((UIScrollView) -> Void)? = nil, |
|
onWillBeginZooming: ((UIScrollView, UIView?) -> Void)? = nil, |
|
onDidChangeAdjustedContentInset: ((UIScrollView) -> Void)? = nil, |
|
onDidEndDragging: ((UIScrollView, Bool) -> Void)? = nil, |
|
onDidEndZooming: ((UIScrollView, UIView?, CGFloat) -> Void)? = nil, |
|
onWillEndDragging: ((UIScrollView, CGPoint, UnsafeMutablePointer<CGPoint>) -> Void)? = nil |
|
) { |
|
self.content = content |
|
self.onDidZoom = onDidZoom |
|
self.onWillBeginDragging = onWillBeginDragging |
|
self.onDidScroll = onDidScroll |
|
self.onDidEndDecelerating = onDidEndDecelerating |
|
self.onWillBeginDecelerating = onWillBeginDecelerating |
|
self.onDidScrollToTop = onDidScrollToTop |
|
self.onShouldScrollToTop = onShouldScrollToTop |
|
self.onDidEndScrollingAnimation = onDidEndScrollingAnimation |
|
self.onWillBeginZooming = onWillBeginZooming |
|
self.onDidChangeAdjustedContentInset = onDidChangeAdjustedContentInset |
|
self.onDidEndDragging = onDidEndDragging |
|
self.onDidEndZooming = onDidEndZooming |
|
self.onWillEndDragging = onWillEndDragging |
|
} |
|
|
|
var body: some View { |
|
ScrollViewReader { proxy in |
|
ScrollView { |
|
content(self.scrollView) |
|
} |
|
.onAppear { |
|
self.scrollViewDelegate = DelegateEnabledScrollViewDelegate( |
|
onDidScroll: onDidScroll ?? { _ in }, |
|
onWillBeginDragging: onWillBeginDragging ?? { _ in }, |
|
onDidEndDecelerating: onDidEndDecelerating ?? { _ in }, |
|
onWillBeginDecelerating: onWillBeginDecelerating ?? { _ in }, |
|
onDidScrollToTop: onDidScrollToTop ?? { _ in }, |
|
onShouldScrollToTop: onShouldScrollToTop ?? { _ in true }, |
|
onDidEndScrollingAnimation: onDidEndScrollingAnimation ?? { _ in }, |
|
onWillBeginZooming: onWillBeginZooming ?? { _, _ in }, |
|
onDidChangeAdjustedContentInset: onDidChangeAdjustedContentInset ?? { _ in }, |
|
onDidEndDragging: onDidEndDragging ?? { _, _ in }, |
|
onDidEndZooming: onDidEndZooming ?? { _, _, _ in }, |
|
onWillEndDragging: onWillEndDragging ?? { _, _, _ in } |
|
) |
|
} |
|
.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { _scrollView in |
|
self.scrollView = _scrollView |
|
self.scrollView!.delegate = self.scrollViewDelegate |
|
} |
|
} |
|
} |
|
|
|
func onDidScroll(_ action: @escaping (_ scrollView: UIScrollView) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onDidScroll = action |
|
return view |
|
} |
|
|
|
func onWillBeginDragging(_ action: @escaping (_ scrollView: UIScrollView) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onWillBeginDragging = action |
|
return view |
|
} |
|
|
|
func onDidEndDecelerating(_ action: @escaping (_ scrollView: UIScrollView) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onDidEndDecelerating = action |
|
return view |
|
} |
|
|
|
func onWillBeginDecelerating(_ action: @escaping (_ scrollView: UIScrollView) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onWillBeginDecelerating = action |
|
return view |
|
} |
|
|
|
func onDidScrollToTop(_ action: @escaping (_ scrollView: UIScrollView) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onDidScrollToTop = action |
|
return view |
|
} |
|
|
|
func onShouldScrollToTop(_ action: @escaping (_ scrollView: UIScrollView) -> Bool) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onShouldScrollToTop = action |
|
return view |
|
} |
|
|
|
func onDidEndScrollingAnimation(_ action: @escaping (_ scrollView: UIScrollView) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onDidEndScrollingAnimation = action |
|
return view |
|
} |
|
|
|
func onWillBeginZooming(_ action: @escaping (_ scrollView: UIScrollView, _ view: UIView?) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onWillBeginZooming = action |
|
return view |
|
} |
|
|
|
func onDidChangeAdjustedContentInset(_ action: @escaping (_ scrollView: UIScrollView) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onDidChangeAdjustedContentInset = action |
|
return view |
|
} |
|
|
|
func onDidEndDragging(_ action: @escaping (_ scrollView: UIScrollView, _ decelerate: Bool) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onDidEndDragging = action |
|
return view |
|
} |
|
|
|
func onDidEndZooming(_ action: @escaping (_ scrollView: UIScrollView, _ view: UIView?, _ scale: CGFloat) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onDidEndZooming = action |
|
return view |
|
} |
|
|
|
func onWillEndDragging(_ action: @escaping (_ scrollView: UIScrollView, _ velocity: CGPoint, _ targetContentOffset: UnsafeMutablePointer<CGPoint>) -> Void) -> DelegateEnabledScrollView { |
|
var view = self |
|
view.onWillEndDragging = action |
|
return view |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class DelegateEnabledScrollViewDelegate: NSObject, UIScrollViewDelegate { |
|
var onDidScroll: ((UIScrollView) -> Void) |
|
var onWillBeginDragging: ((UIScrollView) -> Void) |
|
var onDidEndDecelerating: ((UIScrollView) -> Void) |
|
var onWillBeginDecelerating: ((UIScrollView) -> Void) |
|
var onDidScrollToTop: ((UIScrollView) -> Void) |
|
var onShouldScrollToTop: ((UIScrollView) -> Bool) |
|
var onDidEndScrollingAnimation: ((UIScrollView) -> Void) |
|
var onWillBeginZooming: ((UIScrollView, UIView?) -> Void) |
|
var onDidChangeAdjustedContentInset: ((UIScrollView) -> Void) |
|
var onDidEndDragging: ((UIScrollView, Bool) -> Void) |
|
var onDidEndZooming: ((UIScrollView, UIView?, CGFloat) -> Void) |
|
var onWillEndDragging: ((UIScrollView, CGPoint, UnsafeMutablePointer<CGPoint>) -> Void) |
|
|
|
init( |
|
onDidScroll: @escaping ((UIScrollView) -> Void), |
|
onWillBeginDragging: @escaping ((UIScrollView) -> Void), |
|
onDidEndDecelerating: @escaping ((UIScrollView) -> Void), |
|
onWillBeginDecelerating: @escaping ((UIScrollView) -> Void), |
|
onDidScrollToTop: @escaping ((UIScrollView) -> Void), |
|
onShouldScrollToTop: @escaping ((UIScrollView) -> Bool), |
|
onDidEndScrollingAnimation: @escaping ((UIScrollView) -> Void), |
|
onWillBeginZooming: @escaping ((UIScrollView, UIView?) -> Void), |
|
onDidChangeAdjustedContentInset: @escaping ((UIScrollView) -> Void), |
|
onDidEndDragging: @escaping ((UIScrollView, Bool) -> Void), |
|
onDidEndZooming: @escaping ((UIScrollView, UIView?, CGFloat) -> Void), |
|
onWillEndDragging: @escaping ((UIScrollView, CGPoint, UnsafeMutablePointer<CGPoint>) -> Void) |
|
) { |
|
self.onDidScroll = onDidScroll |
|
self.onWillBeginDragging = onWillBeginDragging |
|
self.onDidEndDecelerating = onDidEndDecelerating |
|
self.onWillBeginDecelerating = onWillBeginDecelerating |
|
self.onDidScrollToTop = onDidScrollToTop |
|
self.onShouldScrollToTop = onShouldScrollToTop |
|
self.onDidEndScrollingAnimation = onDidEndScrollingAnimation |
|
self.onWillBeginZooming = onWillBeginZooming |
|
self.onDidChangeAdjustedContentInset = onDidChangeAdjustedContentInset |
|
self.onDidEndDragging = onDidEndDragging |
|
self.onDidEndZooming = onDidEndZooming |
|
self.onWillEndDragging = onWillEndDragging |
|
} |
|
|
|
func scrollViewDidScroll(_ scrollView: UIScrollView) { |
|
onDidScroll(scrollView) |
|
} |
|
|
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { |
|
onWillBeginDragging(scrollView) |
|
} |
|
|
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { |
|
onDidEndDecelerating(scrollView) |
|
} |
|
|
|
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { |
|
onWillBeginDecelerating(scrollView) |
|
} |
|
|
|
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { |
|
onDidScrollToTop(scrollView) |
|
} |
|
|
|
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { |
|
return onShouldScrollToTop(scrollView) |
|
} |
|
|
|
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { |
|
onDidEndScrollingAnimation(scrollView) |
|
} |
|
|
|
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { |
|
onWillBeginZooming(scrollView, view) |
|
} |
|
|
|
func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { |
|
onDidChangeAdjustedContentInset(scrollView) |
|
} |
|
|
|
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { |
|
onDidEndDragging(scrollView, decelerate) |
|
} |
|
|
|
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { |
|
onDidEndZooming(scrollView, view, scale) |
|
} |
|
|
|
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { |
|
onWillEndDragging(scrollView, velocity, targetContentOffset) |
|
} |
|
} |
|
|
|
|
|
struct _Previewer: View { |
|
@State private var lastEvent = "" |
|
|
|
var body: some View { |
|
GeometryReader { geometry in |
|
VStack { |
|
ScrollView { |
|
Text(lastEvent) |
|
.frame(maxWidth: .infinity, minHeight: geometry.size.height / 3, alignment: .leading) |
|
.background(Color(.systemGray4)) |
|
} |
|
|
|
DelegateEnabledScrollView { scrollView in |
|
LazyVStack { |
|
ForEach(0..<100, id: \.self) { index in |
|
Text("Item \(index)") |
|
.id(index) |
|
} |
|
} |
|
} |
|
.onWillEndDragging { scrollView, velocity, targetContentOffset in |
|
lastEvent = "onWillEndDragging: \(velocity), \(targetContentOffset.pointee)" |
|
} |
|
.frame(height: geometry.size.height * 2 / 3) |
|
} |
|
} |
|
} |
|
} |
|
|
|
#Preview { |
|
_Previewer() |
|
} |