mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
remove combine code from async image
This commit is contained in:
parent
4d887b3505
commit
6e0879c34c
5 changed files with 84 additions and 94 deletions
|
|
@ -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()
|
||||
}
|
||||
81
apple/OmnivoreKit/Sources/Views/AsyncLoadingImage.swift
Normal file
81
apple/OmnivoreKit/Sources/Views/AsyncLoadingImage.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue