I'm trying to display a long list of images with titles using the new AsyncImage in SwiftUI. I noticed that when I put VStack around AsyncImage and scroll through images it's reloading images every time I scroll up or down. When there's no VStack I see no reloading and images seem to stay cached.
Is there a way to make VStack not to reload images on scrolling so I can add text under each image?
Here's a working example. Try scrolling with and without VStack.
import SwiftUI
struct TestView: View {
let url = URL(string: "https://picsum.photos/200/300")
let columns: [GridItem] = [.init(.fixed(110)),.init(.fixed(110)),.init(.fixed(110))]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(0..<20) { _ in
// VStack here causes to images to reload on scrolling
VStack {
AsyncImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Image(systemName: "photo")
.imageScale(.large)
.frame(width: 110, height: 110)
}
}
}
}
}
}
}
I fully agree with you that this is a weird bug. I would guess that the reason it's happening has something to do with the way LazyVGrid
chooses to layout views, and that using a VStack
here gives it the impression there is more than one view to show. This is a poor job on Apple's part, but this is how I solved it: just put the VStacks
internal to the AsyncImage
. I'm not entirely sure what the original error is, but I do know that this fixes it.
struct MyTestView: View {
let url = URL(string: "https://picsum.photos/200/300")
let columns: [GridItem] = [.init(.fixed(110)),.init(.fixed(110)),.init(.fixed(110))]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(0..<20) { i in
AsyncImage(url: url) { image in
VStack {
image
.resizable()
.aspectRatio(contentMode: .fit)
Text("label \(i)")
}
} placeholder: {
VStack {
Image(systemName: "photo")
.imageScale(.large)
.frame(width: 110, height: 110)
Text("image broken")
}
}
}
}
}
}
}