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

SwiftUI动画问题:设置初始状态后无法立即触发动画

解决SwiftUI点击生成圆圈动画异常问题

原代码核心问题分析

  1. 状态更新无通知:AnimatedCircle作为ObservableObject,但width和opacity未添加@Published修饰,属性变化无法触发View更新。
  2. ForEach标识不稳定:用id: \.self作为ForEach的标识,但AnimatedCircle的Equatable实现包含width和opacity,属性变化会导致标识改变,View被强制重建,动画逻辑混乱。
  3. 动画触发时机错误:onAppear中直接修改属性的方式,配合上述问题,导致首次动画不触发,后续点击才补执行。

修复方案及代码实现

1. 重构AnimatedCircle类

添加唯一标识和状态发布器,确保View能稳定识别并响应属性变化:

class AnimatedCircle: ObservableObject, Identifiable {
    let id = UUID() // 唯一ID,避免位置重复或属性变化导致标识失效
    @Published var opacity: Double
    @Published var width: CGFloat
    let position: CGPoint
    
    internal init(opacity: Double = 1, width: CGFloat, position: CGPoint) {
        self.opacity = opacity
        self.width = width
        self.position = position
    }
}

2. 重构主View逻辑

改用withAnimation触发动画,优化ForEach标识,同时解决圆圈残留问题:

public struct SlidePresenterView: View {
    @State private var circles: [AnimatedCircle] = []
    
    public var body: some View {
        GeometryReader { geometry in
            ZStack {
                Color.black.ignoresSafeArea()
                
                ForEach(circles) { circle in
                    Circle()
                        .stroke(style: StrokeStyle(lineWidth: 4))
                        .frame(width: circle.width)
                        .foregroundColor(.white)
                        .position(x: circle.position.x, y: circle.position.y)
                        .opacity(circle.opacity)
                        .animation(.easeInOut(duration: 0.5), value: [circle.width, circle.opacity])
                }
            }
            .onTapGesture { pointTapped in
                let newCircle = AnimatedCircle(width: 40, position: pointTapped)
                circles.append(newCircle)
                
                // 用withAnimation包裹属性修改,确保动画立即触发
                withAnimation(.easeInOut(duration: 0.5)) {
                    newCircle.opacity = 0
                    newCircle.width = 60
                }
                
                // 动画结束后移除圆圈,避免内存占用和残留问题
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    circles.removeAll { $0.id == newCircle.id }
                }
            }
        }
    }
}

修复说明

  • 用UUID作为每个圆圈的唯一标识,ForEach能稳定追踪每个View实例,不会因属性变化重建。
  • @Published修饰属性,确保属性变化时自动通知View更新动画状态。
  • withAnimation直接包裹属性修改,明确触发动画时机,替代不可靠的onAppear方式。
  • 动画结束后自动移除圆圈,解决“始终有一个圆圈停留”的问题,同时避免数组无限增长。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:28:14