4

This used to work fine before but since Xcode 14 if I use an AsyncImage inside a Button or NavigationLink in my list the image loads the first time but when the cell is reused it only displays the placeholder.

Two things I noticed:

  1. clicking on the button immediately fixes the image in that cell
  2. If I remove the Button and add the view with the AsyncImage directly in the ForEach the problem disappears

enter image description here

Code example:

import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ItemListViewModel()
    
    var body: some View {
        List {
            ForEach(viewModel.items, id: \.self) { item in
                Button {

                } label: {
                    HStack {
                        AsyncImage(url: URL(string: "https://www.appatar.io/com.facebook.Facebook")) { phase in
                            if let image = phase.image  {
                                // SHOW IMAGE
                                image
                                    .resizable()
                                    .frame(width: 40, height: 40)

                            } else if phase.error != nil {
                                // FAILED
                                HStack {
                                    Image(systemName: "exclamationmark.triangle")
                                    Text(phase.error?.localizedDescription ?? "")
                                }
                                .padding()

                            } else {
                                // LOADING
                                Color.red
                                    .frame(width: 40, height: 40)
                            }
                        }

                        Text("Item #\(item)")
                    }
                    .padding()
                }
            }
            
            Section {
                ProgressView()
                    .onAppear {
                        viewModel.retrieveDataFromAPI()
                    }
            }
            .navigationBarTitle("Pagination Test")
        }
    }
}

class ItemListViewModel: ObservableObject {
    @Published var items: [Int] = []
    
    func retrieveDataFromAPI(completion: (() -> Void)? = nil) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
            guard let self = self else { return }
            
            let newData = Array(self.items.count...(self.items.count + 50))
            self.items.append(contentsOf: newData)
            completion?()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

James Risner
  • 5,451
  • 11
  • 25
  • 47
Ludyem
  • 1,709
  • 1
  • 18
  • 33
  • some observations; use `@StateObject var viewModel = ItemListViewModel()` and attach the `.onAppear { viewModel.retrieveDataFromAPI() }` to the `List`. Note, `...clicking on the button immediately fixes the image in that cell...`, I don't see any button in the code. Maybe the issue is to do with the server limits. – workingdog support Ukraine Sep 17 '22 at 12:10
  • Yeah sorry I pasted the wrong code, I edited it now to put it back @StateObject had the same issue, and the onAppear it's supposed to be there cause it's faking a pagination and adding more items when you reach the bottom of the list – Ludyem Sep 17 '22 at 14:53
  • Btw I have this issue using KingFisher in my app, I used AsyncImage in the example here to keep it simple since it has the same problem, but Kingfisher should cache the image in theory so I feel like it's not about server limits as even in this example the image is loaded but I have to click the button to make the view reload and show it. Also using the image directly without a button fixes everything so it further proof that the server is not the issue – Ludyem Sep 17 '22 at 14:57

1 Answers1

0

You should use SwiftUI CachedAsyncImage. Very simple and very effective

CachedAsyncImage has the exact same API and behavior as AsyncImage, so you just have to change this:

AsyncImage(url: imageURL)

to this:

CachedAsyncImage(url: imageURL)

https://github.com/lorenzofiamingo/swiftui-cached-async-image

dibs
  • 174
  • 1
  • 12
  • The same issue occurs even if I use CachedAsyncImage, as I said in the comments I'm actually using Kingfisher in my app and the problem is still there even tho the image should be cached – Ludyem Sep 19 '22 at 17:55