我正在使用 SwiftUI 制作一个 iOS 应用程序。该应用程序将在视图中显示大量图像。图像以分层结构存储在云端。我正在尝试一种异步加载每个图像的方法。我想要实现的目标包括:
我尝试过以下(简化的)代码:
@MainActor // <-- Need this to avoid the warning
class Metadata: ObservableObject {
@Published var isLoaded = false // <-- warning if w/o @MainActor: Publishing changes from background threads is not allowed
var images: [ImageData] = []
func load() async {
// Load metadata from cloud...
images.append(ImageData())
images.append(ImageData())
images.append(ImageData())
sleep(1) // To simulate loading
isLoaded = true
}
}
@MainActor // <-- Need this to avoid the warning
class ImageData: ObservableObject {
@Published var isLoaded = false // <-- warning if w/o @MainActor: Publishing changes from background threads is not allowed
var data: String = "N/A" // image data or payload
func load() async {
// Load image data from cloud...
data = "Image data is loaded."
let us = Int.random(in: 1000000..<3000000) // 1 to 3 sec
usleep(UInt32(us)) // To simulate loading
isLoaded = true
}
}
和观点:
struct ContentView: View {
@StateObject var metadata: Metadata = Metadata()
var body: some View {
Group {
if metadata.isLoaded {
VStack {
ForEach(0..<metadata.images.count, id: \.self) { i in
ImageView(image: metadata.images[i])
}
}
} else {
ProgressView()
}
}
.task {
await metadata.load()
}
}
}
struct ImageView: View {
@ObservedObject var image: ImageData
var body: some View {
Group {
if image.isLoaded {
Text(image.data)
} else {
ProgressView()
}
}
.task {
await image.load()
}
}
}
我的问题:
@MainActor
允许视图异步加载每个图像。但是,我收到了警告消息
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
,有时并非所有加载都完成。@MainActor
修复了警告,但所有加载都在主线程上运行,因此需要很长时间才能完成所有图像的加载。此外,用户的交互将被阻止。AsyncImage
,但也想找到一个适用于所有类型数据的通用解决方案,而不仅限于图像。有没有好的方法来处理这种情况?谢谢。
根据@workingdogsupportukraine的建议,以下更改解决了我的问题
class Metadata: ObservableObject {
@Published var isLoaded = false
var images: [ImageData] = []
func load() async {
// Loading metadata from cloud...
images.append(ImageData())
images.append(ImageData())
images.append(ImageData())
do {
try await Task.sleep(nanoseconds: 1 * 1_000_000_000)
} catch {}
DispatchQueue.main.async {
self.isLoaded = true
}
// or
// Task { @MainActor in
// self.isLoaded = true
// }
}
}
class ImageData: ObservableObject {
@Published var isLoaded = false
var data: String = "N/A"
func load() async {
// Loading image data from cloud...
data = "Image data is loaded."
do {
let sec = Int.random(in: 1..<5)
try await Task.sleep(nanoseconds: UInt64(sec) * 1_000_000_000)
} catch {}
DispatchQueue.main.async {
self.isLoaded = true
}
// or
// Task { @MainActor in
// self.isLoaded = true
// }
}
}