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

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)
            }
        }
    }
}

解决方案

问题根源

  1. 错误存储View实例:Status的additionalViewArray存储AdditionalView(View类型)而非数据模型Additional,SwiftUI的View是值类型,不能作为状态存储,会破坏依赖追踪机制。
  2. 未监听子模型变化: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 17:55:54