import UIKit final class ThumbnailCache { static let shared = ThumbnailCache() private let memory = NSCache() private let diskDir: URL private init() { let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] diskDir = caches.appendingPathComponent("NASThumb", isDirectory: true) try? FileManager.default.createDirectory(at: diskDir, withIntermediateDirectories: true) memory.countLimit = 300 memory.totalCostLimit = 80 * 1024 * 1024 // 80 MB } func get(for path: String) -> UIImage? { let key = cacheKey(path) if let img = memory.object(forKey: key as NSString) { return img } let url = diskDir.appendingPathComponent(key) guard let data = try? Data(contentsOf: url), let img = UIImage(data: data) else { return nil } memory.setObject(img, forKey: key as NSString, cost: data.count) return img } func set(_ image: UIImage, for path: String) { let key = cacheKey(path) let data = image.jpegData(compressionQuality: 0.7) ?? Data() memory.setObject(image, forKey: key as NSString, cost: data.count) let url = diskDir.appendingPathComponent(key) try? data.write(to: url, options: .atomic) } private func cacheKey(_ path: String) -> String { path .replacingOccurrences(of: "/", with: "_") .replacingOccurrences(of: ":", with: "-") } } extension UIImage { func thumbnailScaled(to size: CGFloat) -> UIImage { let scale = min(size / self.size.width, size / self.size.height) guard scale < 1 else { return self } let newSize = CGSize(width: self.size.width * scale, height: self.size.height * scale) let renderer = UIGraphicsImageRenderer(size: newSize) return renderer.image { _ in self.draw(in: CGRect(origin: .zero, size: newSize)) } } }