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

SwiftUI 状态变量动画触发视图重载时出现短暂淡入淡出闪烁问题

问题根因

  • 原有实现使用静态数组存储柱形数据,不属于视图状态,无法被SwiftUI自动监听变更,你额外添加的无意义if/else分支会强制整组柱形视图销毁重建,是导致闪烁的核心原因
  • ForEach遍历索引而非元素本身,SwiftUI会默认索引对应位置的视图不变,仅更新高度属性,无法触发位置动画

修复后的完整代码

import SwiftUI

class SortElement: Identifiable{
    var id: Int
    var name: String
    var color: Color
    var height: CGFloat

    init(id: Int, name: String, color: Color, height: CGFloat){
        self.id = id
        self.name = name
        self.color = color
        self.height = height
    }
}

struct SortArrayAnimationDemo: View {
    @Namespace private var animationSwap
    // 将数组改为视图状态变量,支持自动监听变更
    @State private var sortableElements: [SortElement] = [
        SortElement(id: 6, name:"6", color: Color(UIColor.lightGray), height: 60),
        SortElement(id: 2, name:"2", color: Color(UIColor.lightGray), height: 20),
        SortElement(id: 5, name:"5", color: Color(UIColor.lightGray), height: 50),
        SortElement(id: 1, name:"1", color: Color(UIColor.lightGray), height: 10),
        SortElement(id: 3, name:"3", color: Color(UIColor.lightGray), height: 30),
        SortElement(id: 9, name:"9", color: Color(UIColor.lightGray), height: 90),
        SortElement(id: 4, name:"4", color: Color(UIColor.lightGray), height: 40),
        SortElement(id: 8, name:"8", color: Color(UIColor.lightGray), height: 80)
    ]

    var body: some View {
        VStack{
            RoundedRectangle(cornerRadius: 10.0)
                .fill(Color.blue)
                .frame(width: 200.0, height: 20.0)

            // 去掉无意义的if/else分支,直接调用视图构建方法
            fetchSwappingView()

            RoundedRectangle(cornerRadius: 10.0)
                .fill(Color.blue)
                .frame(width: 200.0, height: 20.0)

            Button(action: { doAnimate() }) { 
                Text("Swap!") 
            }.padding(.top, 30)
        }
    }

    @ViewBuilder
    private func fetchSwappingView() -> some View {
        HStack(alignment: .bottom){
            RoundedRectangle(cornerRadius: 10.0)
                .fill(Color.red)
                .frame(width: 30.0, height: 100.0)

            // 直接遍历元素而非索引,匹配元素id做动画
            ForEach(sortableElements) { element in
                RoundedRectangle(cornerRadius: 10.0)
                    .fill(Color.gray)
                    .frame(width: 20.0, height: element.height)
                    .matchedGeometryEffect(id: element.id,
                                   in: animationSwap,
                                   properties: .position)
            }

            RoundedRectangle(cornerRadius: 10.0)
                .fill(Color.red)
                .frame(width: 30.0, height: 100.0)
        }
    }

    private func doAnimate() -> Void {
        let randomIndex = Int.random(in: 1..<8)
        // 交换数组元素,状态变更自动触发视图更新
        sortableElements.swapAt(0, randomIndex)
        // 直接包裹状态变更触发动画,不需要额外toggle状态
        withAnimation(.spring(dampingFraction: 0.7).speed(1.5).delay(0.05)){}
    }
}

修改说明

  1. 将静态数组改为@State修饰的视图私有状态,SwiftUI会自动监听数组的元素变更,差分更新对应视图,无需强制整组重建
  2. ForEach直接遍历遵守Identifiable协议的SortElement实例,系统会根据元素id关联对应视图,配合matchedGeometryEffect自动识别元素的位置变化
  3. 移除了无实际作用的animate状态和if/else分支,彻底解决视图强制销毁重建导致的闪烁问题
  4. 交换数组元素后直接通过withAnimation包裹触发动画,逻辑更简洁,动画流畅无闪烁

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 11:39:03