1

I would like to make sure that when the image is being downloaded the ProgressView is visible and if the url being passed is invalid (empty), a placeholder is put.

How can I do this?

Code:

import Foundation
import SwiftUI

// Download image from URL
struct NetworkImage: View {
    public let url: URL?
    var body: some View {
        Group {
            if let url = url, let imageData = try? Data(contentsOf: url),
               let uiImage = UIImage(data: imageData) {
                Image(uiImage: uiImage)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            }
            else {
                ProgressView()
            }
        }
        
    }
}

struct NetworkImage_Previews: PreviewProvider {
    static var previews: some View {
        let url = [
            "",
            "http://google.com",
            "https://yt3.ggpht.com/a/AATXAJxAbUTyYnKsycQjZzCdL_gWVbJYVy4mVaVGQ8kRMQ=s176-c-k-c0x00ffffff-no-rj"
        ]
        NetworkImage(url: URL(string: url[0])!)
    }
}

pawello2222
  • 46,897
  • 22
  • 145
  • 209
Paul
  • 3,644
  • 9
  • 47
  • 113

1 Answers1

3

You can create a ViewModel to handle the downloading logic:

extension NetworkImage {
    class ViewModel: ObservableObject {
        @Published var imageData: Data?
        @Published var isLoading = false

        private var cancellables = Set<AnyCancellable>()

        func loadImage(from url: URL?) {
            isLoading = true
            guard let url = url else {
                isLoading = false
                return
            }
            URLSession.shared.dataTaskPublisher(for: url)
                .map { $0.data }
                .replaceError(with: nil)
                .receive(on: DispatchQueue.main)
                .sink { [weak self] in
                    self?.imageData = $0
                    self?.isLoading = false
                }
                .store(in: &cancellables)
        }
    }
}

And modify your NetworkImage to display a placeholder image as well:

struct NetworkImage: View {
    @StateObject private var viewModel = ViewModel()

    let url: URL?

    var body: some View {
        Group {
            if let data = viewModel.imageData, let uiImage = UIImage(data: data) {
                Image(uiImage: uiImage)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } else if viewModel.isLoading {
                ProgressView()
            } else {
                Image(systemName: "photo")
            }
        }
        .onAppear {
            viewModel.loadImage(from: url)
        }
    }
}

Then you can use it like:

NetworkImage(url: URL(string: "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png"))

(Note that the url parameter is not force unwrapped).

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • What I would like to do is this, the loading is shown. If the image link is valid it is from a valid result then I show the image. If the link is invalid there are errors, things like that then I show the error placeholder. – Paul Oct 19 '20 at 12:39
  • @Paul Oh, I understand now. Please see the updated answer. – pawello2222 Oct 19 '20 at 16:39
  • If I try with a url like: "https://stackoverflow.design/assets/img/logos/so/logos-stackoverflow.png" It remains in loading If I try with a url like: "http://www.google.com", The loading appears but does not move, it remains stationary does not make its actions... – Paul Oct 19 '20 at 17:35
  • @Paul I'm able to load all links correctly, e.g. `https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png`. Make sure you add the correct scheme (like `https://`) at the beginning of the url. – pawello2222 Oct 19 '20 at 17:52
  • No, I'll explain maybe I understand what the problem could be. Those two links that I indicated to you before, are links that do not lead to any image, in fact the one that should load the stackoverflow logo, I modified it in such a way that it had to give me an error. Being that this test of this file I am testing it using PreviewProvider, it seems that in that case it gives problems, it doesn't work correctly. – Paul Oct 19 '20 at 19:15
  • ```let url = ["", "https://stackoverflow.design/assets/img/logos/so/logo-stackoverflow.png","https://stackoverflow.design/assets/img/logos/so/logos-stackoverflow.png","https://www.google.com","https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png","https://yt3.ggpht.com/a/AATXAJxAbUTyYnKsycQjZzCdL_gWVbJYVy4mVaVGQ8kRMQ=s176-c-k-c0x00ffffff-no-rj"]``` Example url [2] and url [3], it seems that using PreviewProvider gives problems. – Paul Oct 19 '20 at 19:15
  • @Paul That seems to be the issue with PreviewProvider only. It works on a simulator. Just use a real image in previews and test corner cases in a sim or with tests. – pawello2222 Oct 19 '20 at 19:29
  • Thank you, how can I report this issue. Could you report it? – Paul Oct 20 '20 at 09:19
  • @Paul I'm not sure if previews are meant to be used for such corner cases. But if you're sure this is a bug see https://developer.apple.com/bug-reporting/ – pawello2222 Oct 20 '20 at 09:31
  • You know why I am getting the following error: https://i.stack.imgur.com/RME9Z.png – Paul Oct 20 '20 at 12:53
  • @Paul This means you used some view which is not allowed in widgets. I suggest you create a new post describing your problem (as it's related with widgets). – pawello2222 Oct 20 '20 at 13:25
  • I did, if you want to check it out: https://stackoverflow.com/questions/64448711/swiftui-widget-ios-14-problems-with-image-downloaded-from-the-internet – Paul Oct 20 '20 at 15:46
  • 1
    simple and clean answer. also easly modifiable. – Bilal Şimşek Sep 02 '21 at 12:59