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
相关产品推荐
相关产品推荐

