Compare commits

...

2 Commits

+86 -43
View File
@@ -662,6 +662,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var offset = scrollingCollectionView.contentOffset
offset.y *= (screenSize.width / screenSize.height)
scrollingCollectionView.setContentOffset(offset, animated: false)
scrollingCollectionView.reloadData()
}
}
@@ -1156,16 +1157,23 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return
}
progress = newProgress
updateInfo()
var preloadPaths: [URL] = []
let path = getImagePath(progress: progress)
imageLoader.loadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size) { [weak self] image in
self?.imageView.image = image
self?.updateInfo()
}
for _ in 0 ..< preloadCount {
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
let path = getImagePath(progress: newProgress)
imageLoader.preloadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size)
preloadPaths.append(getImagePath(progress: newProgress))
}
imageLoader.setActiveWindow([path])
imageLoader.loadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size) { [weak self] image in
self?.imageView.image = image
}
for path in preloadPaths {
imageLoader.preloadImage(at: path, scaling: scaling, screenSize: UIScreen.main.bounds.size)
}
saveLocalState()
@@ -1527,7 +1535,8 @@ enum Rotation {
class ImageLoader {
private let cache = NSCache<NSString, UIImage>()
private let horCache = NSCache<NSString, UIImage>()
private var loadingTasks: [String: [(UIImage?) -> Void]] = [:]
private var loadingTasks: [String: (UIImage?) -> Void] = [:]
private var activeTokens: [String: CancellationToken] = [:]
init() {
// 128 MiB
@@ -1543,35 +1552,53 @@ class ImageLoader {
screenSize: CGSize,
completion: ((UIImage?) -> Void)?
) {
let rotation: Rotation!
if screenSize.width > screenSize.height {
rotation = Rotation.horizontal
} else { rotation = Rotation.vertical }
let rotation: Rotation = screenSize.width > screenSize.height ? .horizontal : .vertical
let key = path.path
if rotation == .vertical {
if let cached = cache.object(forKey: path.path as NSString) {
completion?(cached)
return
}
} else {
if let cached = horCache.object(forKey: path.path as NSString) {
completion?(cached)
return
}
if rotation == .vertical, let cached = cache.object(forKey: key as NSString) {
completion?(cached); return
}
if rotation == .horizontal, let cached = horCache.object(forKey: key as NSString) {
completion?(cached); return
}
if loadingTasks[path.path] != nil {
if let existing = activeTokens[key], !existing.isCancelled {
if let completion = completion {
loadingTasks[path.path]?.append(completion)
loadingTasks[key] = completion
}
return
}
loadingTasks[path.path] = completion != nil ? [completion!] : []
if let completion = completion {
loadingTasks[key] = completion
}
let token = CancellationToken()
activeTokens[key] = token
queue.async { [weak self] in
guard let self = self else { return }
guard var image = UIImage(contentsOfFile: path.path) else { return }
func finish(_ image: UIImage?) {
DispatchQueue.main.async {
guard self.activeTokens[key] === token else { return }
self.activeTokens.removeValue(forKey: key)
if let image = image {
if let callback = self.loadingTasks.removeValue(forKey: key) {
callback(image)
}
}
}
}
if token.isCancelled { finish(nil); return }
guard var image = UIImage(contentsOfFile: key) else {
finish(nil)
return
}
if token.isCancelled { finish(nil); return }
// If you turn pages fast, completion will not be nil and as such only the needed scaled image should be prepared
if completion == nil || rotation == .vertical {
@@ -1579,37 +1606,38 @@ class ImageLoader {
width: min(screenSize.width, screenSize.height),
height: max(screenSize.height, screenSize.width)
)
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)
guard let cost = imageByteSize(vScaleImage) else { return }
self.cache.setObject(vScaleImage, forKey: path.path as NSString, cost: cost)
if rotation == .vertical {
image = vScaleImage
if let cost = imageByteSize(vScaleImage) {
self.cache.setObject(vScaleImage, forKey: path.path as NSString, cost: cost)
}
if rotation == .vertical { image = vScaleImage }
}
if token.isCancelled { finish(nil); return }
if completion == nil || rotation == .horizontal {
let horizontal = CGSize(
width: max(screenSize.width, screenSize.height),
height: min(screenSize.height, screenSize.width)
)
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)
guard let cost = imageByteSize(hScaleImage) else { return }
self.horCache.setObject(hScaleImage, forKey: path.path as NSString, cost: cost)
if rotation == .horizontal {
image = hScaleImage
if let cost = imageByteSize(hScaleImage) {
self.horCache.setObject(hScaleImage, forKey: path.path as NSString, cost: cost)
}
if rotation == .horizontal { image = hScaleImage }
}
DispatchQueue.main.async {
self.loadingTasks[path.path]?.forEach { $0(image) }
self.loadingTasks.removeValue(forKey: path.path)
}
finish(image)
}
}
func setActiveWindow(_ paths: [URL]) {
let desired = Set(paths.map { $0.path })
for (key, token) in activeTokens where !desired.contains(key) {
token.cancel()
loadingTasks[key] = nil
}
}
@@ -1648,3 +1676,18 @@ func aspectSize(for imageSize: CGSize, in boundingSize: CGSize, scaling: UIView.
height: imageSize.height * scale
)
}
final class CancellationToken {
private var _isCancelled = false
private let lock = NSLock()
var isCancelled: Bool {
lock.lock(); defer { lock.unlock() }
return _isCancelled
}
func cancel() {
lock.lock(); defer { lock.unlock() }
_isCancelled = true
}
}