Kingfisher: Images not showing up reliably in List on iOS 16

Check List

Thanks for considering to open an issue. Before you submit your issue, please confirm these boxes are checked.

Issue Description

What

On iOS 16 (Xcode 14.0.1), KFImages frequently fail to show up when used inside List/ForEach. We’re using them as part of a more complex list item view, but the bug is reproducible using the very simple code below.

Reproduce

import SwiftUI
import Kingfisher

struct ContentView: View {
    private static var imgUrl = URL(string: "https://cdn.iconscout.com/icon/premium/png-48-thumb/kingfisher-3765747-3140298.png")!
    
    var body: some View {
        List {
            ForEach(1...100, id: \.self) { idx in
                KFImage(Self.imgUrl)
                    .frame(width: 48, height: 48)
            }
        }
    }
}

Scroll around a bit and images will disappear and/or fail to appear.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 28 (14 by maintainers)

Commits related to this issue

Most upvoted comments

A new version 7.5.0 was released in which this new modifier is contained. I am closing this for now and if Apple can fix it one day I will append some more comments here.

Thank you all for reporting and helping verify it!

I also submitted feedback for it: FB11564208

I will keep an eye on it, but now I have to say there is no good way to fix it on our end.

I can confirm that this issue has been fixed by Apple in Xcode 14.3 (now still in beta) and iOS 16.4. Since that, I will try to add some version checks in the startLoadingBeforeViewAppear method to skip the workaround in newer iOS versions and mark this method as soft-deprecated.

I added a method in this fix/load-before-view-appear branch. It basically brings back the functionality of loadImmediately back and allows you to load the images as soon as the KFImage body is evaluated if the image is not yet loaded.

I guess it can somehow play as a better workaround before Apple can fix the issue on the SwiftUI side.

To use it:

  1. Change your integration of Kingfisher to fix/load-before-view-appear branch
  2. Add a startLoadingBeforeViewAppear() to the KFImage when you encountered this issue:
KFImage(someURL)
+ .startLoadingBeforeViewAppear()
  .resizable()
  .frame(width: 200, height: 200)

I need some help to try it in more complicate scenes. If it works fine, maybe we can consider including it in the master branch and tagging a release.

@onevcat I can confirm your fix/load-before-view-appear branch has fixed this bug. I had this issue with complex UI: a ListView with Sections, and each section having several KFImages. Now the issue is gone, images are loading correctly with your .startLoadingBeforeViewAppear() modifier.

List(objects) { obj in
                Section(content: {
                    ForEach(...) {
                         KFImage(URL(string: string))
                             .placeholder {
                                ProgressView().progressViewStyle(CircularProgressViewStyle(tint: Color.gray))
                            }
                            .fade(duration: 0.2)
                            .startLoadingBeforeViewAppear()
                            .resizable()
                            .aspectRatio(contentMode: .fit)
               }
         })

Apple’s AsyncImage has the same problem, so it seems the cell life cycle in iOS 16 is now totally broken. Not sure if there is a good way to fix it in the third party side, unless we ignore the onAppear but start the loading in view’s init. However, it obviously brings other problems such as performance issues.

截屏2022-09-21 23 31 59

@windom May I know which version of Kingfisher are you trying? The original issue got a workaround in #1990 and it was already a part of version 7.4.0 and on the master branch. So if you are using these latest versions, I guess it is that workaround is doing its work. But the root cause is not yet addressed.

I used 7.3.2 to avoid the workaround, so I think they fixed it for the simplest case.

@rslifka Previously in the dark age of SwiftUI, there was once a loadImmediately which might be “fixing” this issue. However, it is now removed long ago. Maybe it is actually a good chance to revive it for this issue 😂. I will see if that can work or no.

It’s amazing this iOS 16/16.1 bug… I spent some time trying to find a workaround, but it seems very complicated. Only the workaround with the timer worked for me. But as it has an impact on the performance, I preferred to switch from List to ScrollView + LazyVStack and it seems working well…

Just tried using Xcode 14.1 RC2 and real iOS 16.1 device: the original issue with KFImage directly in List/ForEach seems to be fixed, however the case with nested ForEach-es and Sections is still broken.

Thank you for the many creative workarounds, we will evaluate what to go with.

Sadly 16.1 beta 2 did not fix that much. It fixed this case:

List {
    ForEach(1...100, id: \.self) { idx in
        KFImage(Self.imgUrl)
            .frame(width: 48, height: 48)
    }
}

but not this:

List {
    ForEach(1...100, id: \.self) { idx in
        Button(action: {}) {
            KFImage(Self.imgUrl)
                .frame(width: 48, height: 48)
        }
    }
}

and nor this:

List {
    ForEach(1..<10) { _ in
        Section {
            ForEach(1..<10) { idx in
                KFImage(Self.imgUrl)
                    .frame(width: 48, height: 48)
            }
        }
    }
}

I’ve submitted feedback using AsyncImage. (FB11560572)

onAppear indeed helps in that case, but found a more complex one where nothing seems to help:

import SwiftUI
import Kingfisher

struct ContentView: View {
    private static var imgUrl = URL(string: "https://cdn.iconscout.com/icon/premium/png-48-thumb/kingfisher-3765747-3140298.png")!
    
    var body: some View {
        List {
            ForEach(1..<10) { _ in
                Section {
                    ForEach(1..<10) { idx in
                        KFImage(Self.imgUrl)
                            .frame(width: 48, height: 48)
                    }
                }
            }
        }
    }
}