问题描述
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/