Image not loading when using SwiftUI and Combine

Hello! I have been trying to asynchronously load an image in my app using combine. Currently all the other pieces of data are loading fine, but my image seem to be stuck in a progress view. Why?

This is my code:

import SwiftUI

struct ApodView: View {
    @StateObject var vm = ApodViewModel()
    
    var body: some View {
        ZStack {
            // Background Layer
            Color.theme.background
                .ignoresSafeArea()
            
            // Content Layer
            VStack() {
                Text(vm.apodData?.title ?? "Placeholder")
                    .font(.title)
                    .fontWeight(.bold)
                    .multilineTextAlignment(.center)
                    .foregroundColor(Color.theme.accent)
                
                ApodImageView(apodData: vm.apodData ?? ApodModel(date: "", explanation: "", url: "", thumbnailUrl: "", title: ""))
           
                ZStack() {
                    
                    Color.theme.backgroundTextColor
                    
                    ScrollView(showsIndicators: false) {
                        Text(vm.apodData?.explanation ?? "Loading...")
                            .font(.body)
                            .fontWeight(.semibold)
                            .foregroundColor(Color.theme.accent)
                            .multilineTextAlignment(.center)
                            .padding()
                    }
                }
                .cornerRadius(10)
            }
            .padding()
        }
    }
}

import SwiftUI

struct ApodImageView: View {
    
    @StateObject var vm: ApodImageViewModel
    
    init(apodData: ApodModel) {
        _vm = StateObject(wrappedValue: ApodImageViewModel(apodData: apodData))
    }
    
    var body: some View {
        ZStack {
            if let image = vm.image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
            } else if vm.isLoading {
                ProgressView()
            } else {
                Image(systemName: "questionmark")
                    .foregroundColor(Color.theme.secondaryText)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: 250)
        .cornerRadius(10)
    }
}
import Foundation
import SwiftUI
import Combine

class ApodImageViewModel: ObservableObject {
    
    @Published var image: UIImage?
    @Published var isLoading: Bool = false
    
    private let apodData: ApodModel
    private let dataService: ApodImageService
    private var cancellables = Set<AnyCancellable>()
    
    init(apodData: ApodModel) {
        self.apodData = apodData
        self.dataService = ApodImageService(apodData: apodData)
        self.addSubscribers()
        self.isLoading = true
    }
    
    private func addSubscribers() {
        dataService.$image
            .sink { [weak self] _ in
                self?.isLoading = false
            } receiveValue: { [weak self] returnedImage in
                self?.image = returnedImage
            }
            .store(in: &cancellables)
    }
}
import Foundation
import SwiftUI
import Combine

class ApodImageService: ObservableObject {
    
    @Published var image: UIImage?
    
    private var imageSubscription: AnyCancellable?
    private let apodData: ApodModel
    
    init(apodData: ApodModel) {
        self.apodData = apodData
        getApodImage()
    }
    
    private func getApodImage() {
        guard let url = URL(string: apodData.thumbnailUrl ?? apodData.url) else { return }
        imageSubscription = NetworkingManager.download(url: url)
            .tryMap({ data -> UIImage? in
                return UIImage(data: data)
            })
            .sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] returnedImage in
                self?.image = returnedImage
                self?.imageSubscription?.cancel()
            })
    }
}
import Foundation
import Combine

class NetworkingManager {
    
    enum NetworkingError: LocalizedError {
        case badURLResponse(url: URL)
        case unknown
        
        var errorDescription: String? {
            switch self {
            case .badURLResponse(url: let url): return "Bad response from URL: \(url)"
            case .unknown: return "Unknown Error Returned"
            }
        }
    }
    
    static func download(url: URL) -> AnyPublisher<Data, Error> {
        return URLSession.shared.dataTaskPublisher(for: url)
            .subscribe(on: DispatchQueue.global(qos: .background))
            .tryMap({ try handleURLResponse(output: $0, url: url) })
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
    
    static func handleURLResponse(output: URLSession.DataTaskPublisher.Output, url: URL) throws -> Data {
        guard let response = output.response as? HTTPURLResponse,
              response.statusCode >= 200 && response.statusCode < 300 else {
                  throw NetworkingError.badURLResponse(url: url)
              }
        return output.data
    }
    
    static func handleCompletion(completion: Subscribers.Completion<Error>) {
        switch completion {
        case .finished:
            break
        case .failure(let error):
            print(error.localizedDescription)
        }
    }
}
Terms of Service

Privacy Policy

Cookie Policy