SwiftUI无法追踪数组中对象的内部属性变化问题
SwiftUI无法追踪数组中对象的内部属性变化问题

SwiftUI无法追踪数组中对象的内部属性变化问题

问题描述

SwiftUI存在一种机制:SwiftUI 不会自动追踪数组中对象的内部属性变化。

在SwiftUI中,视图的刷新以来于数据的“变化通知机制”,这一机制依赖以下几个关键点:

1、@State / @Binding:基础数据值,更新时会触发视图刷新。

2、@ObservedObject:明确告诉 SwiftUI,这个对象的 @Published 属性可能变化,变化时会触发视图刷新。

3、@Published var array: [ClassType]:只追踪数组结构变化,只有增删元素时,才会刷新视图。

这也就意味着,如果使用 @ObservedObject监听的对象,虽然添加了 @Published属性,但如果这个对象是数组的话,只有数组结构变化(增加或删减长度)时,才能刷新视图。

例如,我有一个管理图片的类,该类中有一个images属性存储图片,类型为CustomImages:

class AppStorage:ObservableObject {
    static var shared = AppStorage()
    // 存储图片
    @Published var images:[CustomImages] = []
}

class CustomImages: ObservableObject, Identifiable {
    let id: UUID
    let image: NSImage
    let name: String
    @Published var outputSize: Int?
    @Published var outputURL: URL?
    @Published var compressionState:CompressionState
}

当我通过代码修改images属性的CustomImages元素的字段时,即使使用主线程修改CustomImages元素字段,但是SwiftUI并不会触发视图更新。

这一问题就导致,当images数组的元素属性被更新时,SwiftUI并不会更新,所以下面的代码不会随着数组元素的compressionState属性的变化更新,始终保持一个加载的状态,除非数组发生增删操作(数组长度发生变化)。

ForEach(Array(appStorage.images.enumerated()),id: \.offset) { index,item in
    if item.compressionState == .completed {
        Text("下载完成")
    } else if item.compressionState == .pending{
        Text("等待压缩")
    } else if item.compressionState == .failed {
        Text("压缩失败")
    } else {
        // 否则,显示加载状态。
        ProgressView("Loading...")
    }
}

该问题详细请见《Swift图片压缩任务队列调度问题》。

解决方案

这一问题的解决方案,就是在SwiftUI中将数组的元素单独包装观察:

struct ImageView: View {
    @ObservedObject var item: ImageModel

    var body: some View {
        if item.compressionState == .completed {
            Text("下载完成")
        } else if item.compressionState == .pending{
            Text("等待压缩")
        } else if item.compressionState == .failed {
            Text("压缩失败")
        } else {
            // 否则,显示加载状态。
            ProgressView("Loading...")
        }
    }
}

在视图中将数组的元素传入单独包装的观察视图:

ForEach(appStorage.images) { item in
    ImageRowView(item: item)
}

当数组元素的属性发生更新时,SwiftUI就会刷新对应的 ImageView 子视图。

总结

SwiftUI不会自动追踪class类型数组中的元素内部属性的变化,除非显式观察那个元素。

@Published var images: [CustomImages]

这意味着:

images.append(…) ,会触发刷新(数组结构变化)。

images.remove(…) ,会触发刷新(结构变化)。

images[0].isDownloaded = true,不会刷新(结构没变,SwiftUI不会重新渲染)。

这是 SwiftUI 的一个限制:

@Published 只负责追踪变量本身的变化,而不会深入追踪数组内部每个元素的变化(哪怕这些元素是 ObservableObject)。

所以即使 CustomImages 是 class、在里面加了 @Published var isDownloaded = false,也不等于 @Published var images 能感知这个变化。

因此,只有通过显式观察:在SwiftUI中需要将每个显示的元素单独包装观察,才能实现数组对象属性发生变化并触发视图更新的功能。

相关文章

1、Swift图片压缩任务队列调度问题:https://fangjunyu.com/2025/07/12/swift%e5%9b%be%e7%89%87%e5%8e%8b%e7%bc%a9%e4%bb%bb%e5%8a%a1%e9%98%9f%e5%88%97%e8%b0%83%e5%ba%a6%e9%97%ae%e9%a2%98/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注