I’m making an iOS app utilizing SwiftUI. The app may have quite a lot of photos to point out within the views. The pictures are saved on the cloud in a hierarchical construction. I’m making an attempt a option to load every of the photographs asynchronously. The objectives I need to obtain embrace:
- Every picture can load asynchronously and independently.
- The amount and the addresses of the photographs are a part of the metadata saved on the cloud and have to be loaded from the cloud first earlier than loading the picture knowledge.
- Picture loading shouldn’t not block UIs in the primary thread, i.e. customers can nonetheless swipe or press buttons whereas photos are loading.
- Whereas a picture is loading, its placeholder exhibits a progress view.
I’ve tried with the next (simplified) code:
@MainActor // <-- Want this to keep away from the warning
class Metadata: ObservableObject {
@Printed var isLoaded = false // <-- warning if w/o @MainActor: Publishing modifications from background threads will not be allowed
var photos: [ImageData] = []
func load() async {
// Load metadata from cloud...
photos.append(ImageData())
photos.append(ImageData())
photos.append(ImageData())
sleep(1) // To simulate loading
isLoaded = true
}
}
@MainActor // <-- Want this to keep away from the warning
class ImageData: ObservableObject {
@Printed var isLoaded = false // <-- warning if w/o @MainActor: Publishing modifications from background threads will not be allowed
var knowledge: String = "N/A" // picture knowledge or payload
func load() async {
// Load picture knowledge from cloud...
knowledge = "Picture knowledge is loaded."
allow us to = Int.random(in: 1000000..<3000000) // 1 to three sec
usleep(UInt32(us)) // To simulate loading
isLoaded = true
}
}
and the views:
struct ContentView: View {
@StateObject var metadata: Metadata = Metadata()
var physique: some View {
Group {
if metadata.isLoaded {
VStack {
ForEach(0..<metadata.photos.depend, id: .self) { i in
ImageView(picture: metadata.photos[i])
}
}
} else {
ProgressView()
}
}
.activity {
await metadata.load()
}
}
}
struct ImageView: View {
@ObservedObject var picture: ImageData
var physique: some View {
Group {
if picture.isLoaded {
Textual content(picture.knowledge)
} else {
ProgressView()
}
}
.activity {
await picture.load()
}
}
}
My questions:
- Not including
@MainActor
permits the view to load every photos asynchronously. Nonetheless, I acquired the warning message
Publishing modifications from background threads will not be allowed; ensure to publish values from the primary thread (by way of operators like obtain(on:)) on mannequin updates.
and typically not all of the loadings full. - Including
@MainActor
fixes the warnings however all of the loadings run on the primary thread so it takes a really very long time to complete loading all the photographs. Additionally, consumer’s interactions might be blocked. - I’ve additionally thought of utilizing
AsyncImage
, but in addition need to discover a generic resolution for all sorts of knowledge not restricted to pictures.
Is there a great way to deal with this sort of state of affairs? Thanks.
[Updated]
Per @workingdogsupportukraine ‘s suggestion, the next modifications clear up my drawback
class Metadata: ObservableObject {
@Printed var isLoaded = false
var photos: [ImageData] = []
func load() async {
// Loading metadata from cloud...
photos.append(ImageData())
photos.append(ImageData())
photos.append(ImageData())
do {
attempt await Activity.sleep(nanoseconds: 1 * 1_000_000_000)
} catch {}
DispatchQueue.foremost.async {
self.isLoaded = true
}
// or
// Activity { @MainActor in
// self.isLoaded = true
// }
}
}
class ImageData: ObservableObject {
@Printed var isLoaded = false
var knowledge: String = "N/A"
func load() async {
// Loading picture knowledge from cloud...
knowledge = "Picture knowledge is loaded."
do {
let sec = Int.random(in: 1..<5)
attempt await Activity.sleep(nanoseconds: UInt64(sec) * 1_000_000_000)
} catch {}
DispatchQueue.foremost.async {
self.isLoaded = true
}
// or
// Activity { @MainActor in
// self.isLoaded = true
// }
}
}