SwiftUI 滚动文本动画随着用户快速输入而冻结

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

我正在 SwiftUI 中开发一个计数器应用程序,我在其中实现了数字的滚动文本动画。当数字一次增加一个时,动画效果很好。但是,当用户通过快速连续的多个输入快速增加数字时,动画会在恢复之前暂时冻结。

这是我的 RollingText 视图的代码:

//
//  RollingText.swift
//

import SwiftUI

struct RollingText: View {
    var font: Font = .largeTitle
    var weight: Font.Weight = .regular

    @Binding var value: Int
    @State var animationRange: [Int] = []
    @State var isAnimating = false

    var body: some View {
        HStack(spacing: 0) {
            ForEach(0..<animationRange.count, id: \.self) { index in
                Text("8")
                    .font(font)
                    .fontWeight(weight)
                    .opacity(0)
                    .overlay {
                        GeometryReader { proxy in
                            let size = proxy.size

                            VStack(spacing: 0) {
                                ForEach(0...9, id: \.self) { number in
                                    Text("\(number)")
                                        .font(font)
                                        .fontWeight(weight)
                                        .frame(width: size.width, height: size.height, alignment: .center)
                                }
                            }
                            .offset(y: -CGFloat(animationRange[index]) * size.height)
                        }
                        .clipped()
                    }
            }
        }
        .onAppear {
            animationRange = Array(repeating: 0, count: "\(value)".count)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.06) {
                updateText()
            }
        }
        .onChange(of: value) { newValue in
            // 새 값에 따라 animationRange의 길이를 조정합니다.
            adjustAnimationRange(for: newValue)

            // 진행 중인 애니메이션에 대한 목표 값을 업데이트합니다.
            if isAnimating {
                for (index, value) in zip(0..<"\(newValue)".count, "\(newValue)") {
                    animationRange[index] = (String(value) as NSString).integerValue
                }
            } else {
                updateText()
            }
        }
    }
    
    func adjustAnimationRange(for newValue: Int) {
        let newCount = "\(newValue)".count
        let extra = newCount - animationRange.count
        if extra > 0 {
            animationRange += Array(repeating: 0, count: extra)
        } else if extra < 0 {
            animationRange.removeLast(-extra)
        }
    }

    func updateText() {
        isAnimating = true
        let stringValue = "\(value)"
        var longestAnimationDuration = 0.0

        for (index, value) in zip(0..<stringValue.count, stringValue) {
            let fraction = min(Double(index) * 0.15, 0.5)
            let animationDuration = 0.8 + fraction  // 이 값은 애니메이션의 실제 시간에 따라 조정해야 합니다.

            withAnimation(.interactiveSpring(response: 0.8, dampingFraction: 1 + fraction, blendDuration: 1 + fraction)) {
                animationRange[index] = (String(value) as NSString).integerValue
            }

            if animationDuration > longestAnimationDuration {
                longestAnimationDuration = animationDuration
            }
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + longestAnimationDuration) {
            isAnimating = false
        }
    }

}

struct RollingText_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

当用户交互快速、重复时,这个问题似乎就会出现。动画会卡住一小段时间然后继续。我正在寻找一种方法,即使在用户快速输入的情况下,也可以使动画流畅且连续。

如何修改我的 SwiftUI 代码来处理计数器值的快速变化而不中断滚动动画效果?

ios swiftui text rolling-computation effect
1个回答
0
投票
func updateText() {
    isAnimating = true
    let stringValue = "\(value)"
    var longestAnimationDuration = 0.0

    for (index, value) in zip(0..<stringValue.count, stringValue) {
        let fraction = min(Double(index) * 0.15, 0.5)
        let animationDuration = 0.8 + fraction  // Adjust this value based on your animation's actual time.

        withAnimation(.interactiveSpring(response: 0.8, dampingFraction: 1 + fraction, blendDuration: 1 + fraction)) {
            animationRange[index] = (String(value) as NSString).integerValue
        }

        if animationDuration > longestAnimationDuration {
            longestAnimationDuration = animationDuration
        }
    }

    // Cancel ongoing animations before setting new values
    withAnimation(nil) {
        DispatchQueue.main.asyncAfter(deadline: .now() + longestAnimationDuration) {
            isAnimating = false
        }
    }
}

添加

withAnimation(nil)
会取消所有正在进行的动画,使您的后续动画能够无缝开始。当计数器数字快速变化时,此修改应该会增强滚动动画的行为。

© www.soinside.com 2019 - 2024. All rights reserved.