使用 iOS 17 作为最低目标和
.scrollTargetBehavior(.paging)
修饰符。
我需要类似于
scrollViewDidEndScrollingAnimation
的回调。
目标是仅在页面更改且动画完成后获取回调,而不是在滚动过程中或中间左右获取回调。
这不是一个新问题,但我尝试过的许多其他答案都效果不佳。 是否有新的解决方案,特别是如果我的项目的最低 iOS 目标是 iOS17?
在 iOS 18 中,有
onScrollPhaseChange
,因此您不必担心向前兼容性。您可以使用 SwiftUI-Introspect 拦截滚动视图委托调用。
你编写自己的
UIScrollViewDelegate
,包装ScrollView
的内置委托(毕竟SwiftUI也需要为自己的目的设置自己的委托),并转发每个委托方法。
class ScrollViewObserver: NSObject, ObservableObject, UIScrollViewDelegate {
var wrapped: (any UIScrollViewDelegate)?
var action: (() -> Void)?
weak var scrollView: UIScrollView?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
wrapped?.scrollViewDidScroll?(scrollView)
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
wrapped?.scrollViewDidZoom?(scrollView)
}
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
wrapped?.scrollViewDidScrollToTop?(scrollView)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
wrapped?.scrollViewWillBeginDragging?(scrollView)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
wrapped?.scrollViewDidEndDecelerating?(scrollView)
action?()
}
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
wrapped?.scrollViewShouldScrollToTop?(scrollView) ?? true
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
wrapped?.scrollViewWillBeginDecelerating?(scrollView)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
wrapped?.scrollViewDidEndScrollingAnimation?(scrollView)
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
wrapped?.scrollViewWillBeginZooming?(scrollView, with: view)
}
func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
wrapped?.scrollViewDidChangeAdjustedContentInset?(scrollView)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
wrapped?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
wrapped?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
wrapped?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
}
然后在
introspect
中,您可以将滚动视图的委托替换为ScrollViewObserver
,的实例
struct OnScrollEndModifier: ViewModifier {
@StateObject var observer = ScrollViewObserver()
let action: () -> Void
func body(content: Content) -> some View {
if #available(iOS 18, *) {
content
.onScrollPhaseChange { oldPhase, newPhase in
if newPhase == .idle && oldPhase != .idle {
action()
}
}
} else {
content
.introspect(.scrollView, on: .iOS(.v17)) { scrollView in
observer.action = action
// only replace the delegate when this is a new scroll view we haven't seen
if observer.scrollView != scrollView {
observer.wrapped = scrollView.delegate
scrollView.delegate = observer
observer.scrollView = scrollView
}
}
}
}
}
extension View {
func onScrollEnd(_ action: @escaping () -> Void) -> some View {
modifier(OnScrollEndModifier(action: action))
}
}
用途:
ScrollView {
// ...
}
.scrollTargetBehavior(.paging)
.onScrollEnd {
print("Ended!")
}