SwiftUI中For-in循环无法实时更新主视图问题求助
问题描述
在SwiftUI中,使用计算属性内的For-in循环遍历数组时,无法实时更新主视图,只有与UI其他部分交互才会触发更新。具体场景:点击FramingView的按钮向Status的additionalViewArray添加AdditionalView,在PreviewView的计算属性previewImage中遍历该数组生成图片,但修改AdditionalView内的TextField值时,previewImage不会实时更新,只有切换其他TextField时才更新。
相关代码
Status Model
class Status: ObservableObject { @Published var generalData = GeneralData() @Published var sourceData = SourceData() @Published var framingData = FramingData() @Published var additionalViewArray: [AdditionalView] = [] @Published var additionalViewNumber: Int = 1 }
Additional Model
class Additional: ObservableObject { @Published var additionalHorizontalResolution: Int? @Published var additionalVerticalResolution: Int? @Published var additionalOffsetX: Int? @Published var additionalOffsetY: Int? @Published var additionalColor: Color = Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1)) init(additionalHorizontalResolution: Int? = nil, additionalVerticalResolution: Int? = nil, additionalOffsetX: Int? = nil, additionalOffsetY: Int? = nil) { self.additionalHorizontalResolution = additionalHorizontalResolution self.additionalVerticalResolution = additionalVerticalResolution self.additionalOffsetX = additionalOffsetX self.additionalOffsetY = additionalOffsetY } }
AdditionalView
struct AdditionalView: View, Identifiable { @ObservedObject var data: Status @ObservedObject var model: Additional let id = UUID() var number: Int var createImage: NSImage? { if let horizontal = self.model.additionalHorizontalResolution, let vertical = self.model.additionalVerticalResolution { let imageSize = NSSize(width: horizontal, height: vertical) let imageColor = NSColor(model.additionalColor) let image = NSImage(size: imageSize) image.lockFocus() imageColor.setFill() NSBezierPath(rect: NSRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)).fill() image.unlockFocus() return image } return NSImage() } var body: some View { // 自定义视图代码 } }
ContentView
struct ContentView: View { @StateObject var status: Status @StateObject var additional: Additional var body: some View { HStack{ VStack { SourceView(data: status, additional: additional) ScrollView { FramingView(data: status, additional: additional) } } PreviewView(data: status, additional: additional) } } }
FramingView
struct FramingView: View { @ObservedObject var data: Status @ObservedObject var additional: Additional var body: some View { // 自定义视图代码 ForEach(data.additionalViewArray, id: \.id) { view in view } Button(action: { data.additionalViewNumber += 1 data.additionalViewArray.append(AdditionalView(data: data, model: Additional(), number: data.additionalViewNumber)) }) { Label("Add Format", systemImage: "plus.viewfinder") } } }
PreviewView
struct PreviewView: View { @ObservedObject var data: Status @ObservedObject var additional: Additional // 自定义视图代码 var previewImage: NSImage? { let sourceColor = data.sourceData.sourceColor let framingColor = data.framingData.framingColor let NssourceColor = NSColor(sourceColor) let NsframingColor = NSColor(framingColor) guard let sourceHorizontalResolution = data.sourceData.sourceHorizontalResolution, let sourceVerticalResolution = data.sourceData.sourceVerticalResolution else { return placeholderText } let sourceSize = CGSize(width: sourceHorizontalResolution, height: sourceVerticalResolution) let image = NSImage(size: sourceSize) image.lockFocus() NssourceColor.setFill() NSBezierPath(rect: NSRect(origin: .zero, size: sourceSize)).fill() image.unlockFocus() if let framingHorizontalResolution = data.framingData.framingHorizontalResolution, let framingVerticalResolution = data.framingData.framingVerticalResolution { let framingSize = CGSize(width: framingHorizontalResolution, height: framingVerticalResolution) image.lockFocus() NsframingColor.setFill() NSBezierPath(rect: NSRect(x: (sourceSize.width - framingSize.width) / 2, y: (sourceSize.height - framingSize.height) / 2, width: framingSize.width, height: framingSize.height)).fill() image.unlockFocus() } for view in data.additionalViewArray { if let additional = view.createImage { image.lockFocus() additional.draw(at: NSPoint(x: (sourceSize.width - additional.size.width) / 2, y: (sourceSize.height - additional.size.height) / 2), from: NSRect.zero, operation: .sourceOver, fraction: 1.0) image.unlockFocus() } } return image } // 自定义视图代码 var body: some View { VStack(alignment: .trailing) { if let previewImage = previewImage { Image(nsImage: previewImage) .resizable() .scaledToFit() .frame(width: 500, height: 500) } } } }
解决方案
问题根源
- 错误存储View实例:
Status的additionalViewArray存储AdditionalView(View类型)而非数据模型Additional,SwiftUI的View是值类型,不能作为状态存储,会破坏依赖追踪机制。 - 未监听子模型变化:
PreviewView仅监听Status的发布,但Additional模型的属性更新不会触发Status的@Published通知,导致previewImage无法重新计算。
修复步骤
1. 修改Status模型,存储数据模型
将additionalViewArray改为存储Additional模型数组:
class Status: ObservableObject { @Published var generalData = GeneralData() @Published var sourceData = SourceData() @Published var framingData = FramingData() @Published var additionalModels: [Additional] = [] @Published var additionalViewNumber: Int = 1 }
2. 调整FramingView,动态生成视图
点击按钮时添加Additional实例,遍历数据模型生成AdditionalView:
struct FramingView: View { @ObservedObject var data: Status @ObservedObject var additional: Additional var body: some View { // 自定义视图代码 ForEach(data.additionalModels.indices, id: \.self) { index in AdditionalView(data: data, model: data.additionalModels[index], number: index + 1) } Button(action: { data.additionalViewNumber += 1 data.additionalModels.append(Additional()) }) { Label("Add Format", systemImage: "plus.viewfinder") } } }
3. 让PreviewView感知子模型变化
在previewImage中显式访问Additional的所有属性,确保SwiftUI追踪依赖:
struct PreviewView: View { @ObservedObject var data: Status @ObservedObject var additional: Additional // 自定义视图代码 var previewImage: NSImage? { // 显式访问所有需要触发更新的属性,确保依赖追踪 _ = data.additionalModels.map { $0.additionalHorizontalResolution $0.additionalVerticalResolution $0.additionalColor } let sourceColor = data.sourceData.sourceColor let framingColor = data.framingData.framingColor let NssourceColor = NSColor(sourceColor) let NsframingColor = NSColor(framingColor) guard let sourceHorizontalResolution = data.sourceData.sourceHorizontalResolution, let sourceVerticalResolution = data.sourceData.sourceVerticalResolution else { return placeholderText } let sourceSize = CGSize(width: sourceHorizontalResolution, height: sourceVerticalResolution) let image = NSImage(size: sourceSize) image.lockFocus() NssourceColor.setFill() NSBezierPath(rect: NSRect(origin: .zero, size: sourceSize)).fill() image.unlockFocus() if let framingHorizontalResolution = data.framingData.framingHorizontalResolution, let framingVerticalResolution = data.framingData.framingVerticalResolution { let framingSize = CGSize(width: framingHorizontalResolution, height: framingVerticalResolution) image.lockFocus() NsframingColor.setFill() NSBezierPath(rect: NSRect(x: (sourceSize.width - framingSize.width) / 2, y: (sourceSize.height - framingSize.height) / 2, width: framingSize.width, height: framingSize.height)).fill() image.unlockFocus() } for model in data.additionalModels { if let horizontal = model.additionalHorizontalResolution, let vertical = model.additionalVerticalResolution { let imageSize = NSSize(width: horizontal, height: vertical) let imageColor = NSColor(model.additionalColor) let additionalImage = NSImage(size: imageSize) additionalImage.lockFocus() imageColor.setFill() NSBezierPath(rect: NSRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height)).fill() additionalImage.unlockFocus() image.lockFocus() additionalImage.draw(at: NSPoint(x: (sourceSize.width - additionalImage.size.width) / 2, y: (sourceSize.height - additionalImage.size.height) / 2), from: NSRect.zero, operation: .sourceOver, fraction: 1.0) image.unlockFocus() } } return image } // 自定义视图代码 var body: some View { VStack(alignment: .trailing) { if let previewImage = previewImage { Image(nsImage: previewImage) .resizable() .scaledToFit() .frame(width: 500, height: 500) } } } }
4. 优化AdditionalView(可选)
移除AdditionalView中的createImage计算属性,将图片生成逻辑统一到PreviewView或工具类,保持View职责单一。
核心原理
SwiftUI通过追踪视图中访问的@Published属性决定是否刷新,存储View实例会破坏依赖链;显式访问子模型属性可让SwiftUI感知到深层状态变化,触发计算属性重新计算。
内容的提问来源于stack exchange,提问作者Capitan_Jack_89
相关产品推荐
相关产品推荐

