首页 > 解决方案 > 随着时间的推移动画矩形的宽度

问题描述

在 WatchOS 上的 SwiftUI 中,如何为 Rectangle (或任何视图)的宽度设置动画,使其从某个值开始并在指定的时间内动画到不同的值?

具体来说,我想为 Rectangle 设置动画以指示距离下一整分钟或一分钟后的下一个 30 秒的时间。

我看到的所有示例都是基于Timer.scheduledTimer以相对较高的速度发射并设置@State变量,但我的理解是,尤其是在 WatchOS 上应该避免这种情况。有没有更好的办法?

这是我拥有的基于计时器/状态的代码,但我觉得应该有一种更有效的方法:

import SwiftUI

func percentage() -> CGFloat {
    1 - CGFloat(fmod(Date().timeIntervalSince1970, 30) / 30)
}


struct ContentView: View {
    @State var ratio: CGFloat = percentage()

    let timer = Timer.publish(every: 1 / 60, on:.main, in:.common).autoconnect()

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                Rectangle()
                    .foregroundColor(Color.gray)
                    .frame(width:geometry.size.width, height:5)
                HStack {
                    Rectangle()
                        .foregroundColor(Color.red)
                        .frame(width:geometry.size.width * self.ratio, height:5)
                    Spacer()
                }
            }
        }.onReceive(self.timer) { _ in
            self.ratio = percentage()
        }
    }
}

标签: animationswiftuiwatchos

解决方案


我认为使用动画的“更有效的方式”:

struct AnimationRectangle: View {

    struct AnimationRectangle: View {

    @State private var percentage: CGFloat = 0.0
    // count, how much time left to nearest 30 seconds
    @State private var animationDuration = 30 - Double(fmod(Date().timeIntervalSince1970, 30)) 
    private var repeatedAnimationFor30Seconds: Animation {
        return Animation.easeInOut(duration: 30)
            .repeatForever(autoreverses: false)
    }

    var body: some View {

        VStack {

            // just showing duration of current animation
            Text("\(self.animationDuration)")

            ZStack {
                Rectangle()
                    .foregroundColor(.gray)

                GeometryReader { geometry in
                    HStack {
                        Rectangle()
                            .foregroundColor(.green)
                            .frame(width: geometry.size.width * self.percentage)

                        Spacer()
                    }
                }


            }
            .frame(height: 5)
            .onAppear() {

                // first animation without repeating
                withAnimation(Animation.easeInOut(duration: self.animationDuration)) {
                    self.percentage = 1.0
                }

                // other repeated animations
                DispatchQueue.main.asyncAfter(deadline: .now() + self.animationDuration) {
                    self.percentage = 0.0
                    self.animationDuration = 30.0
                    withAnimation(self.repeatedAnimationFor30Seconds) {
                        self.percentage = 1.0
                    }
                }

            }
        }

    }
}
struct AnimationRectangle_Previews: PreviewProvider {
    static var previews: some View {
        AnimationRectangle()
    }
}

推荐阅读