dmytro-anokhin / url-image

AsyncImage before iOS 15. Lightweight, pure SwiftUI Image view, that displays an image downloaded from URL, with auxiliary views and local cache.
MIT License
1.1k stars 96 forks source link

LazyVStack containing a list of images is choppy when scrolling. #84

Closed rico-crescenzio-distilled closed 3 years ago

rico-crescenzio-distilled commented 4 years ago

Summary and/or background When using a LazyVStack contained in ScrollView to render lazily views (for example when working with large amount of data) and these views contain an URLImage, scrolling is very choppy.

OS and what device you are using

Version of URLImage library 0.9.19

What you expected would happen The scroll should be smooth.

What actually happens LazyVStack load a view when it needs to be displayed; this means that during scrolling a lot of views are created and therefore URLImage are created too (so they'll load the image). I'm not sure how Apple implemented LazyVStack, so I don't know if a view is reused (like in UICollectionView) or every time a view is created.

If I change the code to use a simple VStack, everything works good (I assume because all views are loaded just once).

Sample code

Body of the main scene

ScrollView(showsIndicators: false) {
                    LazyVStack {
                        ForEach(viewModel.players) { player in
                            PlayerRowView(player: player) {
                                viewModel.playerDidTap(player)
                            }
                        }
                    }
                    .padding(.horizontal)
                }

Body of PlayerRowView

Button(action: tapAction) {
            HStack(spacing: 16 ) {
                AvatarView(path: player.playerImg)
                    .frame(width: 70, height: 70)
                VStack(alignment: .leading) {
                    Text(player.name)
                        .bold()
                    HStack {
                        flag(for: player.clubImg)
                        flag(for: player.leagueImg)
                        flag(for: player.nationImg)
                    }
                }
                Spacer()
                VStack(alignment: .trailing, spacing: 8) {
                    Text(player.version ?? "")
                        .font(.subheadline)
                        .bold()
                        .lineLimit(1)
                        .minimumScaleFactor(0.3)
                        .foregroundColor(.secondary)
                    Text(String(player.rating ?? 0))
                        .font(.title2)
                        .bold()
                }
            }
            .foregroundColor(.primary)
            .padding()
            .background(Color.backgroundSecondary)
            .cornerRadius(16)
        }

    private func flag(for path: String?) -> some View {
        URLImage(path: path) { $0.image.resizable() }
            .aspectRatio(contentMode: .fit)
            .frame(width: 20, height: 20)
    }

struct AvatarView: View {

    let path: String?

    var body: some View {
        ZStack {
            Circle()
                .foregroundColor(.clear)
                .layoutPriority(1)
            URLImage(path: path) { $0.image.resizable() }
                .aspectRatio(contentMode: .fit)
        }
        .aspectRatio(contentMode: .fit)
        .background(LinearGradient(gradient: Gradient(colors: [.green, .blue]),
                                   startPoint: .bottomLeading,
                                   endPoint: .trailing).opacity(1))
        .clipShape(Circle())
    }

}

Additional information: Video to compare VStack vs LazyVStack usages. Videos.zip

dmytro-anokhin commented 4 years ago

Hey, thank you for reporting and sample code. I'm a bit busy, but I will address this issue when I get time.

abbasmousavi commented 3 years ago

on tvOS when I use image inside NavigationLink that is inside a LazyVStack I get a crash with this error Attempted to read an unowned reference but the object was already deallocated I am using 2.0.0alpha.1

Screen Shot 2020-10-12 at 1 05 41 AM
jackhiggins709 commented 3 years ago

@dmytro-anokhin I am also encountering @rico-crescenzio-distilled's issue. LazyVStack performance is pretty sluggish...

dmytro-anokhin commented 3 years ago

Hey,

I reread your report. If you suspect that loading cause performance degradation you can add a small delay:

URLImage(url, delay: 0.25)

This way download won't start immediately and will be gracefully cancelled when the view disappears. Alternatively, you can try using List since it's reusing views.

I'm currently working on version_2 (#86). You can already try using it. I will definitely look at performance with lazy stacks.

jackhiggins709 commented 3 years ago

Hey @dmytro-anokhin,

Unfortunately, I did not see any performance improvements after adding a .25 delay.

Originally, I was using a List, but due to do limitations in iOS14 we're unable to remove the cell separators like in iOS13, that being said we're required to use LazyVStacks in order to prevent excessive memory usage with traditional VStacks. I think this is going to be a pretty big issue for a lot of people once iOS14 and SwiftUI2 gain more traction.

dmytro-anokhin commented 3 years ago

Please update to v2.0. My test app shows performance improvement with lazy views.

dmytro-anokhin commented 3 years ago

I assume this is fixed if no reply

ricocrescenzio95 commented 3 years ago

Sorry, I actually didn't have chance to test it