Skip to content

Instantly share code, notes, and snippets.

View sgl0v's full-sized avatar

Max sgl0v

View GitHub Profile
@sgl0v
sgl0v / Theme_iOS13.swift
Created October 6, 2019 15:31
Backward compatible Dark Mode on iOS - Theme_iOS13.swift
struct Theme {
static let light = Theme(type: .light, colors: .light)
static let dark = Theme(type: .dark, colors: .dark)
@available(iOS 13.0, *)
static let adaptive = Theme(type: .adaptive, colors: .adaptive)
enum `Type` {
case light
case dark
@available(iOS 13.0, *)
@sgl0v
sgl0v / ThemeProvider_iOS13.swift
Created October 6, 2019 15:31
Backward compatible Dark Mode on iOS - ThemeProvider_iOS13.swift
protocol ThemeProvider: class {
var theme: Theme { get }
func register<Observer: Themeable>(observer: Observer)
func toggleTheme()
}
@available(iOS 13.0, *)
public class DefaultThemeProvider: NSObject, ThemeProvider {
static let shared = DefaultThemeProvider()
let theme: Theme = .adaptive
@sgl0v
sgl0v / ThemeProvider+UITraitEnvironment.swift
Created October 6, 2019 15:32
Backward compatible Dark Mode on iOS - ThemeProvider+UITraitEnvironment.swift
extension Themeable where Self: UITraitEnvironment {
var themeProvider: ThemeProvider {
if #available(iOS 13.0, *) {
return DefaultThemeProvider.shared
} else {
return LegacyThemeProvider.shared
}
}
}
extension UIImage {
func decodedImage() -> UIImage {
guard let cgImage = cgImage else { return self }
let size = CGSize(width: cgImage.width, height: cgImage.height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: cgImage.bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)
context?.draw(cgImage, in: CGRect(origin: .zero, size: size))
guard let decodedImage = context?.makeImage() else { return self }
return UIImage(cgImage: decodedImage)
// Declares in-memory image cache
protocol ImageCacheType: class {
// Returns the image associated with a given url
func image(for url: URL) -> UIImage?
// Inserts the image of the specified url in the cache
func insertImage(_ image: UIImage?, for url: URL)
// Removes the image of the specified url in the cache
func removeImage(for url: URL)
// Removes all images from the cache
func removeAllImages()
final class ImageCache {
// 1st level cache, that contains encoded images
private lazy var imageCache: NSCache<AnyObject, AnyObject> = {
let cache = NSCache<AnyObject, AnyObject>()
cache.countLimit = config.countLimit
return cache
}()
// 2nd level cache, that contains decoded images
private lazy var decodedImageCache: NSCache<AnyObject, AnyObject> = {
extension ImageCache: ImageCacheType {
func insertImage(_ image: UIImage?, for url: URL) {
guard let image = image else { return removeImage(for: url) }
let decodedImage = image.decodedImage()
lock.lock(); defer { lock.unlock() }
imageCache.setObject(decodedImage, forKey: url as AnyObject)
decodedImageCache.setObject(image as AnyObject, forKey: url as AnyObject, cost: decodedImage.diskSize)
}
extension ImageCache {
func image(for url: URL) -> UIImage? {
lock.lock(); defer { lock.unlock() }
// the best case scenario -> there is a decoded image
if let decodedImage = decodedImageCache.object(forKey: url as AnyObject) as? UIImage {
return decodedImage
}
// search for image data
if let image = imageCache.object(forKey: url as AnyObject) as? UIImage {
let decodedImage = image.decodedImage()
extension ImageCache {
subscript(_ key: URL) -> UIImage? {
get {
return image(for: key)
}
set {
return insertImage(newValue, for: key)
}
}
}
final class ImageLoader {
func loadImage(from url: URL) -> AnyPublisher<UIImage?, Never> {
return URLSession.shared.dataTaskPublisher(for: url) ➊
.map { (data, _) -> UIImage? in return UIImage(data: data) } ➋
.catch { error in return Just(nil) } ➌
.subscribe(on: backgroundQueue) ➍
.receive(on: RunLoop.main) ➎
.eraseToAnyPublisher() ➏
}