use separate caches for scaleAspectFill #13

Merged
vegardbm merged 1 commits from scroll-cache into master 2026-07-13 01:01:04 +02:00
+42 -21
View File
@@ -689,8 +689,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var offset = scrollingCollectionView.contentOffset var offset = scrollingCollectionView.contentOffset
offset.y *= (screenSize.width / screenSize.height) offset.y *= (screenSize.width / screenSize.height)
scrollingCollectionView.setContentOffset(offset, animated: false) scrollingCollectionView.setContentOffset(offset, animated: false)
// FIXME: This is a caching issue, needs a different cache for scrolling since it Fill instead of Fit.
scrollingCollectionView.reloadData()
} }
} }
@@ -1569,16 +1567,20 @@ enum Rotation {
} }
class ImageLoader { class ImageLoader {
private let cache = NSCache<NSString, UIImage>() private let portraitFitCache = NSCache<NSString, UIImage>()
private let horCache = NSCache<NSString, UIImage>() private let landscapeFitCache = NSCache<NSString, UIImage>()
private let portraitFillCache = NSCache<NSString, UIImage>()
private let landscapeFillCache = NSCache<NSString, UIImage>()
private var loadingTasks: [String: (UIImage?) -> Void] = [:] private var loadingTasks: [String: (UIImage?) -> Void] = [:]
private var activeTokens: [String: CancellationToken] = [:] private var activeTokens: [String: CancellationToken] = [:]
private var archives = NSCache<NSString, Archive>() private let archives = NSCache<NSString, Archive>()
init() { init() {
// 128 MiB // 128 MiB
cache.totalCostLimit = 128 * 1024 * 1024 portraitFitCache.totalCostLimit = 128 * 1024 * 1024
horCache.totalCostLimit = 128 * 1024 * 1024 landscapeFitCache.totalCostLimit = 128 * 1024 * 1024
portraitFillCache.totalCostLimit = 128 * 1024 * 1024
landscapeFillCache.totalCostLimit = 128 * 1024 * 1024
// The reason archives are cached and have a strict limit of four, is because Archive supposedly opens a posix file pointer and keeps it until the object is destroyed. // The reason archives are cached and have a strict limit of four, is because Archive supposedly opens a posix file pointer and keeps it until the object is destroyed.
archives.countLimit = 4 archives.countLimit = 4
} }
@@ -1587,7 +1589,6 @@ class ImageLoader {
loadImage(archiveURL: archiveURL, filename: filename, scaling: scaling, screenSize: screenSize, completion: nil) loadImage(archiveURL: archiveURL, filename: filename, scaling: scaling, screenSize: screenSize, completion: nil)
} }
// FIXME: So scrolling is not really handled well in terms of caching. This is because it should have a separate cache since it uses a different scaling (Fill instead of Fit).
func loadImage( func loadImage(
archiveURL: URL, filename: String, archiveURL: URL, filename: String,
scaling: UIView.ContentMode, scaling: UIView.ContentMode,
@@ -1597,11 +1598,23 @@ class ImageLoader {
let rotation: Rotation = screenSize.width > screenSize.height ? .horizontal : .vertical let rotation: Rotation = screenSize.width > screenSize.height ? .horizontal : .vertical
let key = filename let key = filename
if rotation == .vertical, let cached = cache.object(forKey: key as NSString) { if scaling == .scaleAspectFit {
completion?(cached); return if rotation == .vertical, let cached = portraitFitCache.object(forKey: key as NSString) {
} completion?(cached); return
if rotation == .horizontal, let cached = horCache.object(forKey: key as NSString) { }
completion?(cached); return if rotation == .horizontal, let cached = landscapeFitCache.object(forKey: key as NSString) {
completion?(cached); return
}
} else if scaling == .scaleAspectFill {
if rotation == .vertical, let cached = portraitFillCache.object(forKey: key as NSString) {
completion?(cached); return
}
if rotation == .horizontal, let cached = landscapeFillCache.object(forKey: key as NSString) {
completion?(cached); return
}
} else {
print("this should not be reachable")
return
} }
if let existing = activeTokens[key], !existing.isCancelled { if let existing = activeTokens[key], !existing.isCancelled {
@@ -1662,8 +1675,14 @@ class ImageLoader {
) )
let vScaledSize = aspectSize(for: image.size, in: vertical, scaling: scaling) let vScaledSize = aspectSize(for: image.size, in: vertical, scaling: scaling)
let vScaleImage = resizeImage(image, to: vScaledSize) let vScaleImage = resizeImage(image, to: vScaledSize)
if let cost = imageByteSize(vScaleImage) { let cost = imageByteSize(vScaleImage)!
self.cache.setObject(vScaleImage, forKey: filename as NSString, cost: cost) if scaling == .scaleAspectFit {
self.portraitFitCache.setObject(vScaleImage, forKey: filename as NSString, cost: cost)
} else if scaling == .scaleAspectFill {
self.portraitFillCache.setObject(vScaleImage, forKey: filename as NSString, cost: cost)
} else {
print("this should be unreachable")
return
} }
if rotation == .vertical { image = vScaleImage } if rotation == .vertical { image = vScaleImage }
} }
@@ -1677,8 +1696,14 @@ class ImageLoader {
) )
let hScaledSize = aspectSize(for: image.size, in: horizontal, scaling: scaling) let hScaledSize = aspectSize(for: image.size, in: horizontal, scaling: scaling)
let hScaleImage = resizeImage(image, to: hScaledSize) let hScaleImage = resizeImage(image, to: hScaledSize)
if let cost = imageByteSize(hScaleImage) { let cost = imageByteSize(hScaleImage)!
self.horCache.setObject(hScaleImage, forKey: filename as NSString, cost: cost) if scaling == .scaleAspectFit {
self.landscapeFitCache.setObject(hScaleImage, forKey: filename as NSString, cost: cost)
} else if scaling == .scaleAspectFill {
self.landscapeFillCache.setObject(hScaleImage, forKey: filename as NSString, cost: cost)
} else {
print("this should be unreachable")
return
} }
if rotation == .horizontal { image = hScaleImage } if rotation == .horizontal { image = hScaleImage }
} }
@@ -1694,10 +1719,6 @@ class ImageLoader {
loadingTasks[key] = nil loadingTasks[key] = nil
} }
} }
func imageIfLoaded(at path: String) -> UIImage? {
return cache.object(forKey: path as NSString)
}
} }
func imageByteSize(_ image: UIImage) -> Int? { func imageByteSize(_ image: UIImage) -> Int? {