【问题标题】:SwiftUI: How to use withAnimation in a ScrollView?SwiftUI:如何在 ScrollView 中使用 withAnimation?
【发布时间】:2021-03-17 00:43:51
【问题描述】:

我一直在尝试使用 ScrollViewReader 和 withAnimation 为 ScrollView 设置动画。

我无法弄清楚为什么这两个动画不起作用,无论是来自 Button 还是 .onAppear?

import SwiftUI

struct ScrollView2: View {
    
    @State private var scrollText = false
    
    var body: some View {
        ScrollViewReader { scrollView in
            ScrollView {
                Button("Scroll to bottom") {
                    withAnimation(.linear(duration: 30)) {
                        scrollView.scrollTo(99, anchor: .center)
                    }
                }
                
                ForEach(0..<100) { index in
                    Text(String(index))
                        .id(index)
                }
                .onAppear(perform: {
                    withAnimation(.linear(duration: 30)) {
                        scrollView.scrollTo(scrollText ? 99 : 1, anchor: .center)
                    }
                    scrollText.toggle()
                })
            }
        }
    }
}

【问题讨论】:

  • 您的代码对我有用。如果您在画布上进行预览,请确保您处于“实时预览”状态。
  • 据报道,只有 .default 动画在这种情况下有效,与您提供的动画无关。
  • 嘿!非常感谢您的 cmets。 @nicksarno 我正在寻找的是动画的 30 秒持续时间,但遗憾的是它不起作用。
  • @Asperi,谢谢我尝试用 .default 替换 .linear,效果很好,但没有持续时间:/
  • @Jean-SébastienWallez 感谢您的更新!我在下面发布了一个选项。

标签: ios animation swiftui scrollview


【解决方案1】:

因为持续时间似乎还不能与 withAnimation 一起使用,所以我必须有点 hacky 才能获得我想要的动画效果。

这就是我所做的:

  1. 我在我的 ScrollView 中添加了一个 ScrollViewReader
  2. 我使用 ForEach 并将 ID 添加到我的 ScrollView 中的项目
  3. 使用 .offset 和 .animation 修饰符为 ScrollView 本身(不是其中的项目)
  4. 使用 .onAppear 中的 .scrollTo 在启动时移动 ScrollView 到离起点较远的项目,以允许用户同时 向后和向前滚动项目,即使 ScrollView 是 本身从右到左动画

我的代码如下所示:

import SwiftUI
import AVKit

struct ProView: View {
    
    @State private var scrollText = false
    
    var body: some View {
        
        ZStack {
            
//            VStack {
//                Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1))
//                    .ignoresSafeArea(.all)
//            }
//            
//            VStack {
//                
//                VideoPlayer(player: AVPlayer(url: Bundle.main.url(forResource: "wave-1", withExtension: "mp4")!)) {
//                    VStack {
//                        Image("pro-text")
//                            .resizable()
//                            .frame(width: 200, height: .infinity)
//                            .scaledToFit()
//                    }
//                }
//                .ignoresSafeArea(.all)
//                .frame(width: .infinity, height: 300)
                
                ScrollView(.horizontal, showsIndicators: false) {
                    
                    ScrollViewReader { value in
                        
                        HStack(spacing: 5) {
                            
                            ForEach(0 ..< 100) { i in
                                
                                HStack {
                                    Image("benefit-1")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-2")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-3")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-4")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-5")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-6")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-7")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-8")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-9")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                    
                                    Image("benefit-10")
                                        .resizable()
                                        .frame(width: 120, height: 120)
                                }
                                .id(i)
                            }
                            
                        }
                        .offset(x: scrollText ? -10000 : 20)
                        .animation(Animation.linear(duration: 300).repeatForever(autoreverses: false))
                        .onAppear() {
                            value.scrollTo(50, anchor: .trailing)
                            scrollText.toggle()
                        }
                    }
                }
                
                Spacer()
            }
            
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ProView()
    }
}

【讨论】:

    【解决方案2】:

    似乎持续时间在 withAnimation 中不起作用。或者,我创建了一个函数来执行一个在 30 秒内触发的重复 Timer,在每个循环中调用 scrollTo withAnimation。

    struct ScrollView2: View {
        
        @State private var scrollText = false
        
        var body: some View {
            ScrollViewReader { scrollView in
                ScrollView {
                    Button("Scroll to bottom") {
                        animateWithTimer(proxy: scrollView)
                    }
                    ForEach(0..<100) { index in
                        Text(String(index))
                            .id(index)
                    }
                }
                
            }
        }
        
        func animateWithTimer(proxy: ScrollViewProxy) {
            let count: Int = 100
            let duration: Double = 30.0
            let timeInterval: Double = (duration / Double(count))
            var counter = 0
            let timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { (timer) in
                withAnimation(.linear) {
                    proxy.scrollTo(counter, anchor: .center)
                }
                counter += 1
                if counter >= count {
                    timer.invalidate()
                }
            }
            timer.fire()
        }
    }
    

    注意:最初按下按钮时会有延迟,因为当它尝试滚动到前约 40 个数字时,它们已经在屏幕上很高,并且 scrollView 不需要滚动到任何地方以使它们居中。您可以根据需要更新 timeInterval 和 counter 变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-05
      • 1970-01-01
      • 2020-03-07
      • 1970-01-01
      • 1970-01-01
      • 2020-11-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多