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

SwiftUI中bouncy/spring类型动画不同步问题求助

问题:SwiftUI中同一@State驱动的弹性动画不同步

在使用同一个@State属性isAnimating驱动多个视图的不同属性动画时,Animation.bouncy或Animation.spring这类弹性动画会很快出现不同步现象,但线性/缓动动画(如linear、easeInOut)能保持同步。该问题在iOS 17.4、Xcode 15.3的预览、真机及模拟器中均可复现。

问题代码

import SwiftUI

struct ContentView: View {
    @State var isAnimating = false

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.yellow)
                .offset(x: isAnimating ? -30 : 0)
                .rotationEffect(isAnimating ? .degrees(-10) : .zero)

            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.green.opacity(0.5))
                .offset(x: isAnimating ? 30 : 0)
                .rotationEffect(isAnimating ? .degrees(10) : .zero)

            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.red)
                .scaleEffect(isAnimating ? 1.1 : 1)
        }
        .frame(width: 100, height: 100)
        .padding()
        .onAppear {
            let animation = Animation.bouncy(duration: 0.5)
                .repeatForever(autoreverses: true)

            withAnimation(animation) {
                isAnimating = true
            }
        }
    }
}

#Preview {
    ContentView()
}

原因分析

弹性动画依赖物理模拟计算运动轨迹,SwiftUI中每个动画属性会独立维护自己的动画状态。当用同一个布尔状态触发多个属性的弹性动画时,每个属性的动画周期会因物理模拟的细微差异逐渐偏移,导致同步失效。而线性/缓动动画基于固定时间进度计算,不会出现此类偏移。

解决方法

方法1:使用Animation.sync()强制同步

SwiftUI提供的sync()修饰符可让多个动画共享同一上下文,强制保持同步,只需在动画定义后添加该修饰符:

.onAppear {
    let animation = Animation.bouncy(duration: 0.5)
        .repeatForever(autoreverses: true)
        .sync() // 添加同步修饰符

    withAnimation(animation) {
        isAnimating = true
    }
}

方法2:用统一进度变量替代布尔值

使用Double类型的@State变量存储动画进度,让所有属性基于同一个进度值计算动画状态,从根源保证同步:

struct ContentView: View {
    @State var progress: Double = 0

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.yellow)
                .offset(x: progress * -30)
                .rotationEffect(.degrees(progress * -10))

            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.green.opacity(0.5))
                .offset(x: progress * 30)
                .rotationEffect(.degrees(progress * 10))

            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.red)
                .scaleEffect(1 + progress * 0.1)
        }
        .frame(width: 100, height: 100)
        .padding()
        .onAppear {
            let animation = Animation.bouncy(duration: 0.5)
                .repeatForever(autoreverses: true)

            withAnimation(animation) {
                progress = 1
            }
        }
    }
}

方法3:用AnimatableModifier统一管理动画

自定义可动画修饰符,将动画逻辑封装到一处,确保所有属性使用同一动画进度:

struct AnimationModifier: AnimatableModifier {
    var progress: Double

    var animatableData: Double {
        get { progress }
        set { progress = newValue }
    }

    func body(content: Content) -> some View {
        content
            .offset(x: progress * 30)
            .rotationEffect(.degrees(progress * 10))
    }
}

// 绿色矩形使用示例:
RoundedRectangle(cornerRadius: 8, style: .continuous)
    .fill(Color.green.opacity(0.5))
    .modifier(AnimationModifier(progress: progress))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 22:08:32