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

SwiftUI中如何利用@Published变量实时触发ForEach更新?

问题:SwiftUI中每次更新数组时立即触发ForEach重绘

在SwiftUI视图中通过ForEach循环绘制图形,当前实现里ViewModel的extractFrameData函数遍历完frameDataArray后,视图的ForEach才会响应frameRectInfoList的更新。需要实现每次在extractFrameData的分支中为frameRectInfoList赋值时,立即触发视图的ForEach执行绘制操作。

视图代码

import SwiftUI

struct RccFrameView: View {
    @EnvironmentObject var frameViewModel: RccFrameViewModel

    var body: some View {
        VStack {
            ForEach(frameViewModel.frameRectInfoList) { frameRectInfo in
                // 基于frameRectInfo绘制图形
            }
        }
        .onReceive(self.frameViewModel.$frameData) { _ in
            frameViewModel.extractFrameData(frameDataArray: frameViewModel.frameData, deviceModel: deviceModel)
        }
    }
}

ViewModel代码

class RccFrameViewModel: ObservableObject {
    @Published var frameData: NSArray = []
    @Published var shapeFillColour = Color.white
    @Published var shapeStrokeColour = Color.white
    @Published var frameRectInfoList: [RccFrameRectInfo] = []
    @Published var currentFrameType: LjFocusFrameType = .unknown
    
    func extractFrameData(frameDataArray: NSArray, deviceModel: RccDeviceModelWrapper) {
        if frameDataArray.count > 0 {
            for frameDict in frameDataArray {
                if let frameType = (frameDict as AnyObject).value(forKey: "frameType") as? Int,
                   let frameRects = (frameDict as AnyObject).object(forKey: "frameRects") as? [RccFrameRectInfo] {
                    currentFrameType = LjFocusFrameType(rawValue: frameType) ?? .unknown
                    
                    switch frameType {
                    case LjFocusFrameType.contrast.rawValue:
                        if frameRects.count > 0 {
                            shapeFillColour = Color("FocusFrameGreen")
                            shapeStrokeColour = .black
                            self.frameRectInfoList = frameRects
                        }
                        
                    case LjFocusFrameType.contrastFlexibleMain.rawValue:
                        if frameRects.count > 0 {
                            shapeFillColour = .white
                            shapeStrokeColour = .black
                            self.frameRectInfoList = frameRects
                        }
                        
                    case LjFocusFrameType.unknown.rawValue:
                        if frameRects.count > 0 {
                            shapeFillColour = .white
                            shapeStrokeColour = .black
                            self.frameRectInfoList = frameRects
                        }
                    default: break
                    }
                }
            }
        }
    }
}

解决方案

核心原因

SwiftUI的ObservableObject会合并同一运行循环(RunLoop)内的多个@Published属性更新,只触发一次视图刷新。所以你在循环中多次给frameRectInfoList赋值,最终只会在循环结束后触发一次重绘。

实现方式:拆分更新到独立运行循环

通过DispatchQueue.main.async将每次赋值操作放到下一个主队列运行循环中,确保每个更新都能单独触发视图刷新。

修改后的extractFrameData函数:

func extractFrameData(frameDataArray: NSArray, deviceModel: RccDeviceModelWrapper) {
    if frameDataArray.count > 0 {
        for frameDict in frameDataArray {
            if let frameType = (frameDict as AnyObject).value(forKey: "frameType") as? Int,
               let frameRects = (frameDict as AnyObject).object(forKey: "frameRects") as? [RccFrameRectInfo] {
                currentFrameType = LjFocusFrameType(rawValue: frameType) ?? .unknown
                
                switch frameType {
                case LjFocusFrameType.contrast.rawValue:
                    if frameRects.count > 0 {
                        shapeFillColour = Color("FocusFrameGreen")
                        shapeStrokeColour = .black
                        // 异步更新,触发即时重绘
                        DispatchQueue.main.async {
                            self.frameRectInfoList = frameRects
                        }
                    }
                    
                case LjFocusFrameType.contrastFlexibleMain.rawValue:
                    if frameRects.count > 0 {
                        shapeFillColour = .white
                        shapeStrokeColour = .black
                        DispatchQueue.main.async {
                            self.frameRectInfoList = frameRects
                        }
                    }
                    
                case LjFocusFrameType.unknown.rawValue:
                    if frameRects.count > 0 {
                        shapeFillColour = .white
                        shapeStrokeColour = .black
                        DispatchQueue.main.async {
                            self.frameRectInfoList = frameRects
                        }
                    }
                default: break
                }
            }
        }
    }
}

备选方案:手动发送变更通知

如果不想用异步队列,也可以在每次赋值前手动发送objectWillChange通知,强制触发视图更新:

// 在赋值前发送变更信号
self.objectWillChange.send()
self.frameRectInfoList = frameRects

不过这种方式在同一运行循环内多次发送通知仍可能被合并,因此DispatchQueue.main.async是更可靠的选择。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 16:07:12