add dependencies folder
This commit is contained in:
parent
d0e3baa960
commit
2218f9e1d9
|
@ -68,7 +68,6 @@ playground.xcworkspace
|
||||||
Carthage/Build/
|
Carthage/Build/
|
||||||
|
|
||||||
# Accio dependency management
|
# Accio dependency management
|
||||||
Dependencies/
|
|
||||||
.accio/
|
.accio/
|
||||||
|
|
||||||
# fastlane
|
# fastlane
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
//
|
||||||
|
// AsyncImage.swift
|
||||||
|
// AsyncImage
|
||||||
|
//
|
||||||
|
// Created by Vadym Bulavin on 2/13/20.
|
||||||
|
// Copyright © 2020 Vadym Bulavin. All rights reserved.
|
||||||
|
//
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
protocol ImageCache {
|
||||||
|
subscript(_ url: URL) -> UIImage? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TemporaryImageCache: ImageCache {
|
||||||
|
private let cache = NSCache<NSURL, UIImage>()
|
||||||
|
|
||||||
|
subscript(_ key: URL) -> UIImage? {
|
||||||
|
get { cache.object(forKey: key as NSURL) }
|
||||||
|
set { newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImageCacheKey: EnvironmentKey {
|
||||||
|
static let defaultValue: ImageCache = TemporaryImageCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
var imageCache: ImageCache {
|
||||||
|
get { self[ImageCacheKey.self] }
|
||||||
|
set { self[ImageCacheKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AsyncImage<Placeholder: View>: View {
|
||||||
|
@StateObject private var loader: ImageLoader
|
||||||
|
private let placeholder: Placeholder
|
||||||
|
private let image: (UIImage) -> Image
|
||||||
|
|
||||||
|
init(
|
||||||
|
url: URL,
|
||||||
|
@ViewBuilder placeholder: () -> Placeholder,
|
||||||
|
@ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
|
||||||
|
) {
|
||||||
|
self.placeholder = placeholder()
|
||||||
|
self.image = image
|
||||||
|
_loader = StateObject(wrappedValue: ImageLoader(url: url, cache: Environment(\.imageCache).wrappedValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.onAppear(perform: loader.load)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var content: some View {
|
||||||
|
Group {
|
||||||
|
if loader.image != nil {
|
||||||
|
image(loader.image!)
|
||||||
|
} else {
|
||||||
|
placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageLoader: ObservableObject {
|
||||||
|
@Published var image: UIImage?
|
||||||
|
|
||||||
|
private(set) var isLoading = false
|
||||||
|
|
||||||
|
private let url: URL
|
||||||
|
private var cache: ImageCache?
|
||||||
|
private var cancellable: AnyCancellable?
|
||||||
|
|
||||||
|
private static let imageProcessingQueue = DispatchQueue(label: "image-processing")
|
||||||
|
|
||||||
|
init(url: URL, cache: ImageCache? = nil) {
|
||||||
|
self.url = url
|
||||||
|
self.cache = cache
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() {
|
||||||
|
guard !isLoading else { return }
|
||||||
|
|
||||||
|
if let image = cache?[url] {
|
||||||
|
self.image = image
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellable = URLSession.shared.dataTaskPublisher(for: url)
|
||||||
|
.map { UIImage(data: $0.data) }
|
||||||
|
.replaceError(with: nil)
|
||||||
|
.handleEvents(receiveSubscription: { [weak self] _ in self?.onStart() },
|
||||||
|
receiveOutput: { [weak self] in self?.cache($0) },
|
||||||
|
receiveCompletion: { [weak self] _ in self?.onFinish() },
|
||||||
|
receiveCancel: { [weak self] in self?.onFinish() })
|
||||||
|
.subscribe(on: Self.imageProcessingQueue)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] in self?.image = $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancel() {
|
||||||
|
cancellable?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onStart() {
|
||||||
|
isLoading = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onFinish() {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cache(_ image: UIImage?) {
|
||||||
|
image.map { cache?[url] = $0 }
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue