Can anyone help me with this? I try to use the LazyVGrid and load images from CoreData stored in a binary data field. How can I make the image loading asynchronous? I have tried to look how ImageLoader's for remote images work, but I can't make it work with images loading from CoreData.
My ImageLoader works but the ScrollView freeze until all images are loaded.
My ImageLoader works but the ScrollView freeze until all images are loaded.
Code Block protocol ImageLoaderModel { func thumbnailData() -> Data? func thumbnailCacheKey() -> String } struct ContentView: View { @FetchRequest( entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)] ) private var items: FetchedResults<Item> var columns: [GridItem] = [ GridItem(.adaptive(minimum: 150)) ] var body: some View { ScrollView { LazyVGrid(columns: columns, alignment: .center, spacing: 20) { ForEach(items, id: \.self) { item in GridItemView(item: item) } } .padding() } } } struct GridItemView: View { @ObservedObject var item: Item var body: some View { VStack(alignment: .center) { if item.thumbnail != nil { AsyncImage(model: item) } else { RoundedRectangle(cornerRadius: 5) .frame(width: 150, height: 112) } Text(item.name ?? "") .font(.subheadline) } } } struct AsyncImage: View { @ObservedObject private var loader: ImageLoader init(model: ImageLoaderModel) { loader = ImageLoader(model: model) } var body: some View { image.onAppear(perform: loader.load) } private var image: some View { Group { if loader.uiImage != nil { Image(uiImage: loader.uiImage!) .resizable() .scaledToFill() .frame(width: 150, height: 112) .cornerRadius(5) .clipped() } else { ProgressView() .frame(width: 150, height: 112) } } } } class ImageLoader: ObservableObject { @Published var uiImage: UIImage? private var model: ImageLoaderModel private var cache = ImageCache.shared init(model: ImageLoaderModel) { self.model = model } func load() { let cacheKey = self.model.thumbnailCacheKey() if let uiImage = cache[cacheKey] { self.uiImage = uiImage return } DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let data = self?.model.thumbnailData() else { return } if let uiImage = UIImage(data: data) { DispatchQueue.main.async { self?.cache[cacheKey] = uiImage self?.uiImage = uiImage return } } } } }