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

如何强制更新由ForEach基于Int创建的SwiftUI网格?

问题描述

我制作了一个由空白色块组成的“棋盘”,要求每秒为其中一个方块填充随机颜色。我已为网格填充空白颜色,每秒应为其中一个方块分配新颜色并重新绘制网格,但不清楚如何让SwiftUI执行此更新。

原代码
struct AnimationObject: Hashable, Equatable {
    var hashValue: Int {
        return self.placement.0.hashValue + self.placement.1.hashValue
    }
    static func == (lhs: AnimationObject, rhs: AnimationObject) -> Bool {
        return lhs.placement == rhs.placement
    }
    
    
    var color: Color
    let placement: (Int, Int)
    
    static func emptyArray(rows: Int, colums: Int) -> [AnimationObject] {
        var emptyArray = [AnimationObject]()
        for i in 0...rows {
            for k in 0...colums {
                emptyArray.append(.init(color: .clear, placement: (i,k)))
            }
        }
        return emptyArray
    }
}


struct AnimatedCanvas: View {
    
    @State var colors = AnimationObject.emptyArray(rows: 4, colums: 4)
    
    let size = CGSize(width: 30, height: 30)
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        Grid(horizontalSpacing: 0, verticalSpacing: 0) {
            ForEach(0..<4) { i in
                ForEach(0..<4) { j in
                    let color = colors.filter { $0.placement.0 == i && $0.placement.1 == j}.first!
                    Rectangle()
                        .fill(color.color)
                        .frame(width: 30, height: 30)
                }
            }.padding(0)
            
        }.onReceive(timer) { input in
            let i = Int.random(in: 0..<4)
            let j = Int.random(in: 0..<4)
            var color = colors.filter { $0.placement.0 == i && $0.placement.1 == j}.first!
            color.color = Color.random()
            if colors.count > 30 {
                colors.removeAll()
            }
        }
    }
}
问题原因及修复方案

核心问题

  1. 数组元素修改无效:AnimationObject是值类型,在onReceive中你复制了数组里的元素并修改,但这个修改不会同步回原数组,SwiftUI监测不到@State数组的变化,所以不会更新视图。
  2. 数组初始化范围错误:emptyArray方法中用了0...rows和0...colums,当传入rows=4时,会生成5行5列共25个元素,但视图里只渲染4x4=16个方块,导致元素数量不匹配。
  3. 无效的数组清空逻辑:初始化后的数组只有16个元素,colors.count >30永远为false,这段代码不会执行。

修复后的代码

import SwiftUI

// 实现Color的随机扩展
extension Color {
    static func random() -> Color {
        Color(
            red: Double.random(in: 0...1),
            green: Double.random(in: 0...1),
            blue: Double.random(in: 0...1)
        )
    }
}

struct AnimationObject: Hashable, Equatable {
    var color: Color
    let placement: (Int, Int)
    
    static func == (lhs: AnimationObject, rhs: AnimationObject) -> Bool {
        return lhs.placement == rhs.placement
    }
    
    static func emptyArray(rows: Int, columns: Int) -> [AnimationObject] {
        var emptyArray = [AnimationObject]()
        // 用半开范围..<,确保生成rows行columns列的元素
        for i in 0..<rows {
            for k in 0..<columns {
                emptyArray.append(.init(color: .clear, placement: (i,k)))
            }
        }
        return emptyArray
    }
}

struct AnimatedCanvas: View {
    @State var colors = AnimationObject.emptyArray(rows: 4, columns: 4)
    
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        Grid(horizontalSpacing: 0, verticalSpacing: 0) {
            ForEach(0..<4) { i in
                GridRow {
                    ForEach(0..<4) { j in
                        // 通过索引直接取元素,比filter更高效
                        let colorObj = colors[i * 4 + j]
                        Rectangle()
                            .fill(colorObj.color)
                            .frame(width: 30, height: 30)
                    }
                }
            }
        }
        .onReceive(timer) { _ in
            let row = Int.random(in: 0..<4)
            let col = Int.random(in: 0..<4)
            // 计算元素在数组中的索引,直接修改原数组的元素
            let index = row * 4 + col
            colors[index].color = Color.random()
            
            // 优化重置逻辑:当所有方块都被填充颜色时,重置为全透明数组
            let filledCount = colors.filter { $0.color != .clear }.count
            if filledCount >= 16 {
                colors = AnimationObject.emptyArray(rows: 4, columns: 4)
            }
        }
    }
}

关键修改点

  • 直接通过索引修改@State数组中的元素,确保SwiftUI能监测到数组变化并更新视图。
  • 修正数组初始化的循环范围,让元素数量和视图渲染的方块数量一致。
  • 替换低效的filter查找,改用索引直接访问元素,提升性能。
  • 优化重置逻辑:当所有方块都被填充颜色时,重置为全透明数组。
  • 补充了Color.random()的实现,确保代码可运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 17:03:39