remove combine code from async image

This commit is contained in:
Satindar Dhillon 2022-04-27 21:40:32 -07:00
parent 4d887b3505
commit 6e0879c34c
5 changed files with 84 additions and 94 deletions

View file

@ -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<Content: View>: 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<AnyCancellable>()
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<Data, BasicError> {
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()
}

View file

@ -0,0 +1,81 @@
import Foundation
import Models
import SwiftUI
import Utils
enum AsyncImageStatus {
case loading
case loaded(image: Image)
case error
}
struct AsyncLoadingImage<Content: View>: 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")
}
}

View file

@ -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()

View file

@ -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()

View file

@ -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 {