Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LazyImage(url:) does not load image when using ImageRenderer #760

Open
mergesort opened this issue Feb 29, 2024 · 3 comments
Open

LazyImage(url:) does not load image when using ImageRenderer #760

mergesort opened this issue Feb 29, 2024 · 3 comments
Labels
improvement Non-functional change that improves existing functionality

Comments

@mergesort
Copy link

Hey @kean, thank you so much for this library, it's been a true life-saver!

I noticed in this changelog entry you mention that LazyImage should render an image when using ImageRenderer, but when I try to use ImageRenderer I'm not seeing this work.

In the screenshot below the [C] and the empty box on the bottom right are powered by LazyImage, code that looks approximately like this. (The boxed [C] is a static letter drawn when an image can't be loaded.)

extension LinkView {
    struct ImageView: View {
        let imageURL: URL

        var body: some View {
            LazyImage(url: imageURL)
                .animation(ImagePipeline.shared.cache.containsData(for: ImageRequest(url: imageURL)) ? nil : .easeIn(duration: 0.1))
        }
    }
}

Easy Vegetarian Recipes

The code I have for ImageRenderer is relatively simple but I've noticed I have the same issue in widgets, so perhaps I'm missing some sort of configuration that will load the image correctly.

@MainActor
func render(link: RichLink, width: CGFloat) -> UIImage? {
    let linkView = LinkView(link: link, annotationsDisplayStyle: .minimal)

    let renderer = ImageRenderer(content: linkView)
    renderer.scale = 3.0

    return renderer.uiImage
}

I have three questions, two of which pertain to this problem directly:

  1. Do you see anything I'm doing wrong with regards to loading images in ImageRenderer (or widgets)?
  2. Is there a way to force images to load synchronously, and would that bypass the issue?
  3. What is the best way to only animate images in place when the image is not already cached? That last one is unrelated but I noticed it in the code snippet above.

Just wanted to say thank you so much again, Nuke really has made my life a lot easier!

@kean
Copy link
Owner

kean commented Mar 5, 2024

Hey, @mergesort 👋

when using ImageRenderer, but when I try to use ImageRenderer I'm not seeing this work.

Can you please confirm if you see the same behavior when the image is available in the memory cache?

If there is an issue even in that scenario, it might be related to the changes introduced in 12.1.3 that temporarily removed the memory cache lookup from the first body call and haven't yet gotten to refactoring it to bring it back. I'm considering switching to .task for loading images and also adding memory cache lookup somewhere in the init.

Do you see anything I'm doing wrong with regards to loading images in ImageRenderer (or widgets)?
What is the best way to only animate images in place when the image is not already cached? That last one is unrelated but I noticed it in the code snippet above.

This part is a bit inefficient:

.animation(ImagePipeline.shared.cache.containsData(for: ImageRequest(url: imageURL)) ? nil : .easeIn(duration: 0.1))

LazyImage init has a transaction parameter that will run animations if the image is loaded not from the memory cache. It's a slightly different behavior, but I would suggest giving it a try or disabling animations altogether, which is my personal preference.

Is there a way to force images to load synchronously, and would that bypass the issue?

Not really, but LazyImage has an onCompletion closure that you might find useful.

@dankimio
Copy link

dankimio commented Mar 25, 2024

Can you please confirm if you see the same behavior when the image is available in the memory cache?

Hi @kean @mergesort 👋

I'm experiencing the same issue. I can confirm that it persists even when the image is available in the memory cache. In my case, the images are loaded on the initial screen. Subsequently, in a child view, an ImageRenderer renders a view with these images.

I'm uncertain if it's a bug because ImageRenderer exhibits similar behavior when used with AsyncImage.

Since these images are already loaded at the time ImageRenderer renders a view, I figured I could load those images from cache, but I'm stuck because the imageTask runs asynchronously.

@kean kean added the improvement Non-functional change that improves existing functionality label Apr 20, 2024
@justinswart
Copy link

I had some success with this by storing the rendered image in a @State var and updating it in onCompletion:

@State private var renderedImage: Image?

var body: some View {
    // ...
}

@MainActor
func updateRenderedImage() {
    let renderer = ImageRenderer(content: body)
    renderer.scale = displayScale
    renderedImage = renderer.uiImage.flatMap(Image.init)
}

// usage
LazyImage(url: url) { state in
    if let image = state.image {
        image
            .resizable()
            .aspectRatio(contentMode: .fit)
    } else {
        Color.blue
    }
}
.onCompletion { _ in
    Task { @MainActor in
        updateRenderedImage()
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
improvement Non-functional change that improves existing functionality
Projects
None yet
Development

No branches or pull requests

4 participants