You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何实现自定义时长的ScrollView内容偏移动画

问题原因

动画时长不生效是SwiftUI的API限制导致的:原生ScrollViewReader提供的scrollTo(_:anchor:)方法不支持自定义动画时长,即使在外层包裹withAnimation传入10秒这类自定义时长参数,系统也会强制使用内置的默认滚动动画时长,不会响应自定义配置。
目标效果参考:
慢速滚动效果演示

实现方案

要完全控制滚动动画的时长、缓动效果,最可靠的方式是桥接底层UIKit的UIScrollView能力:通过辅助视图拿到SwiftUI ScrollView对应的底层UIScrollView实例,之后就可以用UIKit的动画API实现任意时长的滚动效果,和你在UIKit中给contentOffset加动画的逻辑完全一致。
可直接运行的完整实现代码如下:

struct TestItem:Identifiable {
    var id = UUID()
    var index:Int
}

// 辅助视图:查找SwiftUI ScrollView对应的底层UIScrollView实例
struct ScrollViewFinder: UIViewRepresentable {
    let onFind: (UIScrollView) -> Void
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        DispatchQueue.main.async {
            if let scrollView = view.findSuperview(of: UIScrollView.self) {
                onFind(scrollView)
            }
        }
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {}
}

extension UIView {
    func findSuperview<T: UIView>(of type: T.Type) -> T? {
        guard let superview = superview else { return nil }
        if let matched = superview as? T {
            return matched
        }
        return superview.findSuperview(of: type)
    }
}

struct ContentView: View {
    
    @State var testItems = [TestItem]()
    let itemsUpperBound:Int = 1000
    // 持有底层UIScrollView引用
    @State private var targetScrollView: UIScrollView?
    @State var trigger = false
 
    var columnGridItems = [
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    var body: some View {
        
        VStack {
            HStack {
                Button {
                    trigger.toggle()
                } label: {
                    Text("TEST")
                        .foregroundColor(.white)
                        .padding()
                        .background(.blue)
                }
                Spacer()
            }
            
            ScrollView(.horizontal) {
                LazyHGrid(rows: columnGridItems) {
                    ForEach(testItems) { ti in
                        Text("id \(ti.id)")
                            .id(ti.id)
                    }
                }
                .border(.blue)
                // 注入辅助视图获取UIScrollView实例
                .background(ScrollViewFinder { scrollView in
                    self.targetScrollView = scrollView
                })
                .onChange(of: trigger) { newValue in
                    guard let scrollView = targetScrollView else { return }
                    // 计算可滚动的最大X偏移
                    let maxOffsetX = max(0, scrollView.contentSize.width - scrollView.bounds.width)
                    // 示例:随机滚动到任意位置,可替换为固定的末尾偏移实现全内容自动滚动
                    let targetOffsetX = CGFloat.random(in: 0...maxOffsetX)
                    
                    // 完全自定义动画配置,和UIKit用法一致
                    UIView.animate(
                        withDuration: 10, // 传入任意需要的动画时长
                        delay: 0,
                        options: [.curveEaseInOut, .allowUserInteraction],
                        animations: {
                            // 注意这里animated参数要传false,否则会覆盖自定义动画
                            scrollView.setContentOffset(CGPoint(x: targetOffsetX, y: 0), animated: false)
                        },
                        completion: nil
                    )
                }
            }
            .border(.red)
            
        }
        .onAppear {
            for i in 0..<itemsUpperBound {
                testItems.append(TestItem(index: i))
            }
        }
    }
}
使用说明
  • 代码中withDuration参数支持传入任意自定义时长,同时支持所有UIKit提供的动画缓动曲线,完全满足自定义动画需求。
  • 如果要实现自动展示全部内容的效果,只需要把动画块中的targetOffsetX替换为maxOffsetX,即可实现从当前位置缓慢滚动到内容末尾的效果,和参考动图一致。
  • 动画配置中加入.allowUserInteraction是为了保证滚动过程中用户可以随时手动打断滑动,和原生ScrollView交互逻辑一致。
  • 调用setContentOffset时必须把animated参数设为false,否则系统会使用默认动画覆盖自定义的时长配置。
  • 如果需要滚动到指定条目对应的位置,可以配合GeometryReader读取目标条目的frame位置,转换为对应的contentOffset值传入即可。

内容的提问来源于stack exchange,提问作者zumzum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:27:22