Skip to content

Instantly share code, notes, and snippets.

@fal3
Last active April 13, 2023 22:02
Show Gist options
  • Save fal3/d0e46fa7266feb87336c5a16c8cb1913 to your computer and use it in GitHub Desktop.
Save fal3/d0e46fa7266feb87336c5a16c8cb1913 to your computer and use it in GitHub Desktop.
A UIImageView subclass that doesn't use 3rd Party libraries and has an activity indicator built in while loading.
import UIKit
import Foundation
class CachedImageView: UIImageView {
private let activityIndicatorView: UIActivityIndicatorView = {
let aiv = UIActivityIndicatorView(style: .large)
aiv.translatesAutoresizingMaskIntoConstraints = false
return aiv
}()
override init(frame: CGRect) {
super.init(frame: frame)
layoutActivityIndicator()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func layoutActivityIndicator() {
activityIndicatorView.removeFromSuperview()
addSubview(activityIndicatorView)
activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
if self.image == nil {
activityIndicatorView.startAnimating()
}
}
// MARK: - Properties
func downloadImageFrom(url: URL) {
ImageCache.publicCache.load(url: url as NSURL) { image in
guard let image = image else { return }
DispatchQueue.main.async { [weak self] in
self?.image = image
self?.activityIndicatorView.stopAnimating()
}
}
}
}
public class ImageCache {
public static let publicCache = ImageCache()
var cachedImages = NSCache<NSURL, UIImage>() //NSCache requires nsurl
public final func image(url: NSURL) -> UIImage? {
return cachedImages.object(forKey: url)
}
/// - Tag: cache
// Returns the cached image if available, otherwise asynchronously loads and caches it.
private func download(_ url: NSURL, completion: @escaping (UIImage?) -> ()) {
// Go fetch the image.
URLSession.shared.dataTask(with: url as URL) { data, response, error in
guard let responseData = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
// Check for the error, then data and try to create the image.
if let image = UIImage(data: responseData) {
// Cache the image.
self.cachedImages.setObject(image, forKey: url, cost: responseData.count)
DispatchQueue.main.async {
completion(image)
}
} else {
DispatchQueue.main.async {
completion(nil)
}
return
}
}.resume()
}
private func pullFromCache(url: NSURL, completion: @escaping (UIImage?) -> ()) {
// Check for a cached image.
if let cachedImage = image(url: url) {
DispatchQueue.main.async {
completion(cachedImage)
}
return
}
}
final func load(url: NSURL, completion: @escaping (UIImage?) -> ()) {
pullFromCache(url: url, completion: completion)
download(url, completion: completion)
}
}
import SwiftUI
import Combine
struct AsyncImageView: View {
@StateObject private var imageLoader = ImageLoader()
let url: URL
let placeholder: UIImage
init(url: URL, placeholder: UIImage = UIImage(systemName: "photo")!) {
self.url = url
self.placeholder = placeholder
}
var body: some View {
Group {
if let image = imageLoader.image {
Image(uiImage: image)
.resizable()
} else {
VStack {
Image(uiImage: placeholder)
.resizable()
if imageLoader.isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
}
}
}
.onAppear {
imageLoader.load(from: url)
}
}
}
class ImageLoader: ObservableObject {
@Published var image: UIImage?
@Published var isLoading = false
private var cancellable: AnyCancellable?
func load(from url: URL) {
isLoading = true
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.sink { [weak self] image in
self?.image = image
self?.isLoading = false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment