AsyncImage not rendering all images in a List and producing error Code =-999 "cancelled"
Asked Answered
M

2

10

I'm having trouble getting all images to show in a List using AsyncImage. When you first run the app, it seems fine but if you start scrolling, some Images aren't shown. Either the ProgressView doesn't update until the row is scrolled outside of the screen and back into view or I get an error Code=-999 "cancelled".

I can get the all the images to show and ProgressView to update correctly using a regular Image View and going through the whole setup process of downloading and showing an image. It's only when I try to use AsyncImage that all the images aren't shown. How can I get all AsyncImages to show in a List?

struct ContentView: View {
    
    @StateObject private var viewModel = ListViewModel()
    
    var body: some View {
        List {
            ForEach(viewModel.images) { photo in
                HStack {
                    AsyncImage(url: URL(string: photo.thumbnailUrl)) { phase in
                        switch phase {
                        case .success(let image):
                            image
                        case .failure(let error):
                            let _ = print(error)
                            Text("error: \(error.localizedDescription)")
                        case .empty:
                            ProgressView()
                        @unknown default:
                            fatalError()
                        }
                    }
                    
                    VStack(alignment: .leading) {
                        Text(photo.title)
                        Text(photo.thumbnailUrl)
                    }
                }
            }
        }
        .task {
            await viewModel.loadImages()
        }
    }
}

@MainActor 
class ListViewModel: ObservableObject {
    
    @Published var images: [PhotoObject] = []
    
    func loadImages() async {
        do {
            let photos = try await AsyncImageManager.downloadImages()
                images = photos
        } catch {
            print("Could load photos: \(error)")
        }
    }
    
}

struct AsyncImageManager {
    
    static func downloadImages() async throws -> [PhotoObject] {
        let url = URL(string: "https://jsonplaceholder.typicode.com/photos")!
        
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode([PhotoObject].self, from: data)
    }
}

struct PhotoObject: Identifiable, Codable {
    let albumId: Int
    let id: Int
    let title: String
    let url: String
    let thumbnailUrl: String
}

enter image description here

enter image description here

enter image description here

Mott answered 31/7, 2022 at 0:18 Comment(12)
I could not replicate your issue, works well for me. I scrolled slowly, fast then very fast, and still all is working. I'm on macos 13 Ventura, using xcode 14.0-beta4, target macOS-13 and IOS-15.6. What system are you using?Spitzer
@workingdogsupportUkraine I'm on macOS Monterey 12.5, using Xcode 14.3 and deployments of macOS-12.4 and iOS-16.0. I am currently downloading Xcode 14.4. I will test it out and get back to you.Mott
@workingdogsupportUkraine So I tested it on Xcode 14.4 and Xcode 13.4.1. It still does the same thing. I changed target deployments around too but nothing worked. I'm going to edit my question and add a video of the bug.Mott
Correction, I can now see the error being displayed in some places. I could not see any before, there are no errors in the console however.Spitzer
Just uploaded the gifs. Looks like you have to click on them to get them to play. First time uploading gifs. I tested it on simulator but I will try and test on my device. Also seems like Xcode 13 reproduces the error message more and Xcode 14.4 reproduces the ProgressView not updating to the image more.Mott
it could be "them" at the server end, refusing the connection (e.g rate limiting), not "us" at the client end, requesting the image.Spitzer
Check out Karen Prater's Loading and caching images from URL in SwiftUI - AsyncImage or custom view model logic on youtube. She talks about AsyncImage not working well with a List. You can see the issue so clearly when she runs the app.Mott
I don't think it's on the server end. I have 2 other working examples one using combine and one without combine using a regular Image View. They don't reproduce the issue at all. It has to be AsyncImage.Mott
I have exactly the same issue hereHarve
I've given up on AsyncImage, Kingfisher perfectly solved my problemHarve
@Harve Yea I have seen in videos Kingfisher's works really good. I already have two other working ones but I figured I would post the issue with AsyncImage because I really didn't see many people talking about it and was wondering If anyone had a workaround. I also submitted feedback to Apple as well but haven't heard back from them.Mott
@Mott great, thanks for your effort bringing this up!Harve
A
7

This is still a bug in iOS 16. The only solution that I found is to use ScrollView + LazyVStack instead of List.

Alleras answered 17/11, 2022 at 15:22 Comment(2)
I'm seeing the same issue. I tried using ScrollView + LazyVStack with the ForEach code inside but it still didn't work correctly. I was still seeing error cancelled in place of the image.Mott
@Mott Did you try with another API? Since some of them have a rate limit. This solution solved it for me directly.Alleras
C
0

Did you try to add ".fixedSize()" on your AsyncImage object? Like this:

AsyncImage(
     url: URL(string: "https://XXXXX.png"),
     content: { image in
           image.resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 100, height: 100)
     },
     placeholder: {
            ProgressView()
            .frame(width: 100, height: 100)
})
.fixedSize()
Chucklehead answered 8/2 at 14:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.