在 iOS 17 SwiftUI 中检测 ScrollView 何时完成滚动

问题描述 投票:0回答:1

使用 iOS 17 作为最低目标和

.scrollTargetBehavior(.paging)
修饰符。

我需要类似于

scrollViewDidEndScrollingAnimation
的回调。 目标是仅在页面更改且动画完成后获取回调,而不是在滚动过程中或中间左右获取回调。

这不是一个新问题,但我尝试过的许多其他答案都效果不佳。 是否有新的解决方案,特别是如果我的项目的最低 iOS 目标是 iOS17?

swiftui scrollview ios17
1个回答
0
投票

在 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!")
}
© www.soinside.com 2019 - 2024. All rights reserved.