4.8 C
London
Thursday, March 28, 2024

ios – Learn how to load a number of knowledge asynchronously in Swift?


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:

  1. Every picture can load asynchronously and independently.
  2. 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.
  3. Picture loading shouldn’t not block UIs in the primary thread, i.e. customers can nonetheless swipe or press buttons whereas photos are loading.
  4. 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:

  1. 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.
  2. 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.
  3. 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
        // }
    }
}

Latest news
Related news

LEAVE A REPLY

Please enter your comment!
Please enter your name here