Compare commits
2 Commits
e74b5f003f
...
400febc7b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
400febc7b3
|
|||
|
193bef4def
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user