From 6e0879c34cdbb6dde83fc3b7b6ebddbf87a39900 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 27 Apr 2022 21:40:32 -0700 Subject: [PATCH] remove combine code from async image --- .../Sources/Views/AsyncImage.swift | 91 ------------------- .../Sources/Views/AsyncLoadingImage.swift | 81 +++++++++++++++++ .../Sources/Views/FeedItem/GridCard.swift | 2 +- .../Views/FeedItem/HomeFeedCardView.swift | 2 +- .../Sources/Views/ProfileCard.swift | 2 +- 5 files changed, 84 insertions(+), 94 deletions(-) delete mode 100644 apple/OmnivoreKit/Sources/Views/AsyncImage.swift create mode 100644 apple/OmnivoreKit/Sources/Views/AsyncLoadingImage.swift diff --git a/apple/OmnivoreKit/Sources/Views/AsyncImage.swift b/apple/OmnivoreKit/Sources/Views/AsyncImage.swift deleted file mode 100644 index 454263ecf..000000000 --- a/apple/OmnivoreKit/Sources/Views/AsyncImage.swift +++ /dev/null @@ -1,91 +0,0 @@ -import Combine -import Foundation -import Models -import SwiftUI -import Utils - -enum AsyncImageStatus { - case loading - case loaded(image: Image) - case error -} - -struct AsyncImage: View { - let viewBuilder: (AsyncImageStatus) -> Content - let url: URL - @StateObject private var imageLoader = ImageLoader() - - init(url: URL, @ViewBuilder viewBuilder: @escaping (AsyncImageStatus) -> Content) { - self.url = url - self.viewBuilder = viewBuilder - } - - var body: some View { - viewBuilder(imageLoader.status) - .onAppear { - imageLoader.load(fromUrl: url) - } - } -} - -private final class ImageLoader: ObservableObject { - @Published var status: AsyncImageStatus = .loading - var loadStarted = false - - private var subscriptions = Set() - - func load(fromUrl url: URL) { - guard !loadStarted else { return } - loadStarted = true - - if let cachedImage = ImageCache.shared[url] { - #if os(iOS) - status = .loaded(image: Image(uiImage: cachedImage)) - #else - status = .loaded(image: Image(nsImage: cachedImage)) - #endif - return - } - - fetch(url: url).sink( - receiveCompletion: { [weak self] completion in - guard case .failure = completion else { return } - self?.status = .error - }, receiveValue: { [weak self] data in - #if os(iOS) - let fetchedImage = UIImage(data: data) - #else - let fetchedImage = NSImage(data: data)#endif - guard let fetchedImage = fetchedImage else { - self?.status = .error - return - } - ImageCache.shared[url] = fetchedImage - - #if os(iOS) - self?.status = .loaded(image: Image(uiImage: fetchedImage)) - #else - self?.status = .loaded(image: Image(nsImage: fetchedImage)) - #endif - } - ) - .store(in: &subscriptions) - } -} - -private func fetch(url: URL) -> AnyPublisher { - let request = URLRequest(url: url) - - return URLSession.DataTaskPublisher(request: request, session: .shared) - .tryMap { data, response in - guard let httpResponse = response as? HTTPURLResponse, 200 ..< 300 ~= httpResponse.statusCode else { - throw BasicError.message(messageText: "failed") - } - return data - } - .mapError { _ in - BasicError.message(messageText: "failed") - } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() -} diff --git a/apple/OmnivoreKit/Sources/Views/AsyncLoadingImage.swift b/apple/OmnivoreKit/Sources/Views/AsyncLoadingImage.swift new file mode 100644 index 000000000..2490bdc9a --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/AsyncLoadingImage.swift @@ -0,0 +1,81 @@ +import Foundation +import Models +import SwiftUI +import Utils + +enum AsyncImageStatus { + case loading + case loaded(image: Image) + case error +} + +struct AsyncLoadingImage: View { + let viewBuilder: (AsyncImageStatus) -> Content + let url: URL + @StateObject private var imageLoader = ImageLoader() + + init(url: URL, @ViewBuilder viewBuilder: @escaping (AsyncImageStatus) -> Content) { + self.url = url + self.viewBuilder = viewBuilder + } + + var body: some View { + viewBuilder(imageLoader.status) + .task { await imageLoader.load(fromUrl: url) } + } +} + +@MainActor private final class ImageLoader: ObservableObject { + @Published var status: AsyncImageStatus = .loading + var loadStarted = false + + func load(fromUrl url: URL) async { + guard !loadStarted else { return } + loadStarted = true + + if let cachedImage = ImageCache.shared[url] { + #if os(iOS) + status = .loaded(image: Image(uiImage: cachedImage)) + #else + status = .loaded(image: Image(nsImage: cachedImage)) + #endif + return + } + + if let imageData = try? await fetchImageData(url: url) { + #if os(iOS) + let fetchedImage = UIImage(data: imageData) + #else + let fetchedImage = NSImage(data: imageData) + #endif + + guard let fetchedImage = fetchedImage else { + status = .error + return + } + ImageCache.shared[url] = fetchedImage + + #if os(iOS) + status = .loaded(image: Image(uiImage: fetchedImage)) + #else + status = .loaded(image: Image(nsImage: fetchedImage)) + #endif + } else { + status = .error + } + } +} + +private func fetchImageData(url: URL) async throws -> Data { + do { + let (data, response) = try await URLSession.shared.data(from: url) + + if let httpResponse = response as? HTTPURLResponse, 200 ..< 300 ~= httpResponse.statusCode { + return data + } else { + throw BasicError.message(messageText: "failed") + } + } catch { + throw BasicError.message(messageText: "failed") + } +} diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index b239e62e4..f9e5c49ba 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -127,7 +127,7 @@ public struct GridCard: View { Spacer() if let imageURL = item.imageURL { - AsyncImage(url: imageURL) { imageStatus in + AsyncLoadingImage(url: imageURL) { imageStatus in if case let AsyncImageStatus.loaded(image) = imageStatus { image .resizable() diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift index 217f6f140..7576b0ab0 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift @@ -39,7 +39,7 @@ public struct FeedCard: View { Group { if let imageURL = item.imageURL { - AsyncImage(url: imageURL) { imageStatus in + AsyncLoadingImage(url: imageURL) { imageStatus in if case let AsyncImageStatus.loaded(image) = imageStatus { image .resizable() diff --git a/apple/OmnivoreKit/Sources/Views/ProfileCard.swift b/apple/OmnivoreKit/Sources/Views/ProfileCard.swift index f74e1d328..61583e480 100644 --- a/apple/OmnivoreKit/Sources/Views/ProfileCard.swift +++ b/apple/OmnivoreKit/Sources/Views/ProfileCard.swift @@ -23,7 +23,7 @@ public struct ProfileCard: View { HStack(alignment: .center) { Group { if let url = data.imageURL { - AsyncImage(url: url) { imageStatus in + AsyncLoadingImage(url: url) { imageStatus in if case let AsyncImageStatus.loaded(image) = imageStatus { image.resizable() } else {