cache image dimensions and (chapter, page) used for scrolling, fix bug related to width and height of UICollectionViewCell
This commit is contained in:
@@ -3,11 +3,15 @@
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>uploadBitcode</key>
|
||||
<false/>
|
||||
<key>uploadSymbols</key>
|
||||
<key>method</key>
|
||||
<string>debugging</string>
|
||||
<key>signingStyle</key>
|
||||
<string>automatic</string>
|
||||
<key>destination</key>
|
||||
<string>export</string>
|
||||
<key>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
<key>compileBitcode</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
//TODO: Implement support for bonus chapters
|
||||
//TODO: Implement Anilist support?
|
||||
//TODO: Properly avoid swallowing of input from UICollectionView used for scrolling
|
||||
//TODO: Fix UICollectionView sizeForItemAt method performance either by caching or lazy loading
|
||||
//TODO: Convert between state for normal and scrolling page turn
|
||||
|
||||
import UIKit
|
||||
|
||||
let preloadCount = 3
|
||||
let preloadCount = 2
|
||||
// With a whopping 2 cores, and 2 threads because no hyperthreading, it makes sense to only have a queue as an extra thread to do work with to allow the main thread for ui.
|
||||
let queue = DispatchQueue(label: "queue", qos: .utility)
|
||||
|
||||
enum PageTurn {
|
||||
case next
|
||||
@@ -69,6 +71,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
var scrollPos: CGPoint!
|
||||
var hasSetContentOffset = false
|
||||
var pageCount = 0
|
||||
var pagesAvailable = 0
|
||||
var sizeList: [CGSize] = []
|
||||
var chapterAndPages: [(Int, Int)] = []
|
||||
|
||||
var imageView = UIImageView()
|
||||
var mode = PageTurnMode.leftToRight
|
||||
@@ -106,12 +111,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
|
||||
let imageLoader = ImageLoader.init()
|
||||
|
||||
let localStateQueue = DispatchQueue(label: "state.local.queue", qos: .background)
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = .clear
|
||||
view.backgroundColor = .white
|
||||
|
||||
setup()
|
||||
}
|
||||
@@ -249,10 +252,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
case .leftToRight:
|
||||
ReadProgress.leftToRight(chapter: self.currentChapter, page: self.currentPage)
|
||||
case .rightToLeft:
|
||||
ReadProgress.leftToRight(chapter: self.currentChapter, page: self.currentPage)
|
||||
ReadProgress.rightToLeft(chapter: self.currentChapter, page: self.currentPage)
|
||||
case .scroll: ReadProgress.scroll(scrollingCollectionView.contentOffset)
|
||||
}
|
||||
localStateQueue.async {
|
||||
queue.async {
|
||||
do {
|
||||
try JSONEncoder().encode(
|
||||
LocalState(
|
||||
@@ -285,6 +288,17 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
case .scroll(let point):
|
||||
if self.scrollPos == nil {
|
||||
self.scrollPos = point
|
||||
let centerPoint = CGPoint(
|
||||
x: scrollingCollectionView.bounds.midX
|
||||
+ scrollingCollectionView.contentOffset.x,
|
||||
y: scrollingCollectionView.bounds.midY
|
||||
+ scrollingCollectionView.contentOffset.y)
|
||||
|
||||
if let indexPath = scrollingCollectionView.indexPathForItem(at: centerPoint) {
|
||||
let (chapter, page) = chapterAndPages[indexPath.item]
|
||||
currentChapter = chapter
|
||||
currentPage = page
|
||||
}
|
||||
}
|
||||
self.mode = .scroll
|
||||
}
|
||||
@@ -313,13 +327,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
y: scrollingCollectionView.bounds.midY + scrollingCollectionView.contentOffset.y)
|
||||
|
||||
if let indexPath = scrollingCollectionView.indexPathForItem(at: centerPoint) {
|
||||
var index = 0
|
||||
var (chapter, page) = (1, 1)
|
||||
while index < indexPath.item {
|
||||
(chapter, page) = getChapterAndPageFromTurn(
|
||||
chapter: chapter, page: page, turn: .next)
|
||||
index += 1
|
||||
}
|
||||
let (chapter, page) = chapterAndPages[indexPath.item]
|
||||
currentChapter = chapter
|
||||
currentPage = page
|
||||
}
|
||||
@@ -330,7 +338,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
if scrollingCollectionView.isHidden == false && mode == .scroll && !hasSetContentOffset {
|
||||
if scrollingCollectionView.isHidden == false && mode == .scroll
|
||||
&& !hasSetContentOffset
|
||||
{
|
||||
scrollingCollectionView.setContentOffset(scrollPos, animated: false)
|
||||
hasSetContentOffset = true
|
||||
}
|
||||
@@ -343,7 +353,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
if let path = getPathFromComicName(name: name) {
|
||||
currentPath = path
|
||||
metadata = getMetadata(path: path)!
|
||||
pageCount = metadata.chapters.map { $0.pages }.reduce(0, +)
|
||||
|
||||
globalState.comicName = metadata.title
|
||||
saveGlobalState()
|
||||
loadLocalState()
|
||||
@@ -353,14 +363,80 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
rightView.isHidden = false
|
||||
setImages(path: path, metadata: metadata)
|
||||
} else {
|
||||
leftView.isHidden = true
|
||||
rightView.isHidden = true
|
||||
scrollingCollectionView.isHidden = false
|
||||
scrollingCollectionView.reloadData()
|
||||
setupScrolling()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupScrolling() {
|
||||
leftView.isHidden = true
|
||||
rightView.isHidden = true
|
||||
scrollingCollectionView.isHidden = false
|
||||
pagesAvailable = countFiles()
|
||||
var index = 0
|
||||
var (chapter, page) = (1, 1)
|
||||
chapterAndPages = Array(repeating: (0, 0), count: pagesAvailable + 1)
|
||||
chapterAndPages[index] = (chapter, page)
|
||||
while index < pagesAvailable {
|
||||
index += 1
|
||||
(chapter, page) = getChapterAndPageFromTurn(
|
||||
chapter: chapter, page: page, turn: .next)
|
||||
chapterAndPages[index] = (chapter, page)
|
||||
}
|
||||
|
||||
sizeList = Array(repeating: CGSize(), count: pagesAvailable)
|
||||
let binPath = currentPath.appendingPathComponent("size.bin")
|
||||
let decoder = PropertyListDecoder()
|
||||
do {
|
||||
let data = try Data(contentsOf: binPath)
|
||||
sizeList = try decoder.decode([CGSize].self, from: data)
|
||||
} catch {
|
||||
print("Failed to read data:", error)
|
||||
}
|
||||
|
||||
scrollingCollectionView.reloadData()
|
||||
DispatchQueue.main.async {
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .binary
|
||||
do {
|
||||
let data = try encoder.encode(self.sizeList)
|
||||
try data.write(to: binPath)
|
||||
} catch {
|
||||
print("Encoding or writing failed:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func countFiles() -> Int {
|
||||
var count = 0
|
||||
if let enumerator = fileManager.enumerator(
|
||||
at: currentPath, includingPropertiesForKeys: [.isDirectoryKey],
|
||||
options: [.skipsHiddenFiles])
|
||||
{
|
||||
for case let dir as URL in enumerator {
|
||||
do {
|
||||
if let enumerator = fileManager.enumerator(
|
||||
at: dir, includingPropertiesForKeys: [.isRegularFileKey],
|
||||
options: [.skipsHiddenFiles])
|
||||
{
|
||||
for case let file as URL in enumerator {
|
||||
let resourceValues = try file.resourceValues(forKeys: [
|
||||
.isRegularFileKey
|
||||
])
|
||||
if resourceValues.isRegularFile == true {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("Error reading file attributes for \(dir):", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func setupScrollingCollectionView() {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.minimumInteritemSpacing = 0
|
||||
@@ -368,7 +444,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
scrollingCollectionView = UICollectionView(
|
||||
frame: view.bounds, collectionViewLayout: layout)
|
||||
scrollingCollectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollingCollectionView.isUserInteractionEnabled = true
|
||||
scrollingCollectionView.dataSource = self
|
||||
scrollingCollectionView.delegate = self
|
||||
scrollingCollectionView.backgroundColor = .white
|
||||
@@ -383,7 +458,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
scrollingCollectionView.trailingAnchor.constraint(equalTo: readerView.trailingAnchor),
|
||||
scrollingCollectionView.heightAnchor.constraint(equalTo: readerView.heightAnchor),
|
||||
])
|
||||
|
||||
}
|
||||
|
||||
func setupTapZones() {
|
||||
@@ -639,16 +713,28 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
|
||||
@objc func handlePageTurnOption(_ sender: UIButton) {
|
||||
if let title = sender.currentTitle {
|
||||
let prev = mode
|
||||
switch title.lowercased() {
|
||||
case "left to right": mode = .leftToRight
|
||||
case "right to left": mode = .rightToLeft
|
||||
case "scroll":
|
||||
if mode != .scroll {
|
||||
scrollingCollectionView.reloadData()
|
||||
}
|
||||
mode = .scroll
|
||||
case "scroll": mode = .scroll
|
||||
default: break
|
||||
}
|
||||
if prev == mode { return }
|
||||
if mode == .scroll {
|
||||
setupScrolling()
|
||||
} else {
|
||||
imageLoader.loadImage(
|
||||
at: getImagePath(
|
||||
chapter: currentChapter,
|
||||
volume: Int(metadata.chapters[currentChapter - 1].volume),
|
||||
page: currentPage,
|
||||
path: currentPath)
|
||||
) {
|
||||
[weak self] image in
|
||||
self?.imageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
scrollingCollectionView.isHidden = mode != .scroll
|
||||
leftView.isHidden = mode == .scroll
|
||||
@@ -824,7 +910,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
|
||||
if let chapterTitle = metadata.chapters[currentChapter - 1].title {
|
||||
text =
|
||||
text + "\(chapterTitle)"
|
||||
text + "\(chapterTitle)\n"
|
||||
}
|
||||
text =
|
||||
text + """
|
||||
@@ -1002,7 +1088,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
||||
if collectionView == comicCollectionView {
|
||||
return comics.count
|
||||
} else {
|
||||
return pageCount
|
||||
return pagesAvailable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,20 +1110,26 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
||||
if metadata == nil {
|
||||
return cell
|
||||
}
|
||||
var index = 0
|
||||
var (chapter, page) = (1, 1)
|
||||
while index < indexPath.item {
|
||||
(chapter, page) = getChapterAndPageFromTurn(
|
||||
chapter: chapter, page: page, turn: .next)
|
||||
index += 1
|
||||
}
|
||||
var (chapter, page) = chapterAndPages[indexPath.item]
|
||||
if let url = getImagePath(
|
||||
chapter: chapter,
|
||||
volume: Int(metadata.chapters[chapter - 1].volume),
|
||||
page: page, path: currentPath)
|
||||
{
|
||||
// cell.imageView.image = UIImage(contentsOfFile: url.path)
|
||||
imageLoader.loadImage(at: url) { image in cell.imageView.image = image }
|
||||
for _ in 0...preloadCount {
|
||||
(chapter, page) = getChapterAndPageFromTurn(
|
||||
chapter: chapter, page: page, turn: .next)
|
||||
if let url = getImagePath(
|
||||
chapter: chapter,
|
||||
volume: Int(metadata.chapters[chapter - 1].volume),
|
||||
page: page, path: currentPath)
|
||||
{
|
||||
imageLoader.preloadImage(at: url)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return cell
|
||||
} else {
|
||||
@@ -1066,13 +1158,13 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
||||
if metadata == nil {
|
||||
return CGSize()
|
||||
}
|
||||
var index = 0
|
||||
var (chapter, page) = (1, 1)
|
||||
while index < indexPath.item {
|
||||
(chapter, page) = getChapterAndPageFromTurn(
|
||||
chapter: chapter, page: page, turn: .next)
|
||||
index += 1
|
||||
if sizeList[indexPath.item] != CGSize(width: 0, height: 0) {
|
||||
return CGSize(
|
||||
width: readerView.bounds.width,
|
||||
height: sizeList[indexPath.item].height * readerView.bounds.width
|
||||
/ sizeList[indexPath.item].width)
|
||||
}
|
||||
let (chapter, page) = chapterAndPages[indexPath.item]
|
||||
if let imagePath = getImagePath(
|
||||
chapter: chapter,
|
||||
volume: Int(metadata.chapters[chapter - 1].volume),
|
||||
@@ -1080,11 +1172,15 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
||||
let imageSource = CGImageSourceCreateWithURL(imagePath as CFURL, nil),
|
||||
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
|
||||
as? [CFString: Any],
|
||||
let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat
|
||||
let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat,
|
||||
let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat
|
||||
{
|
||||
return CGSize(width: readerView.bounds.width, height: height)
|
||||
sizeList[indexPath.item] = CGSize(width: width, height: height)
|
||||
return CGSize(
|
||||
width: readerView.bounds.width, height: height * readerView.bounds.width / width
|
||||
)
|
||||
}
|
||||
return CGSize(width: readerView.bounds.width, height: readerView.bounds.height)
|
||||
return CGSize()
|
||||
} else {
|
||||
assert(false)
|
||||
}
|
||||
@@ -1152,7 +1248,6 @@ class ComicImageCell: UICollectionViewCell {
|
||||
class ImageLoader {
|
||||
private let cache = NSCache<NSString, UIImage>()
|
||||
private var loadingTasks: [String: [((UIImage?) -> Void)]] = [:]
|
||||
private let queue = DispatchQueue(label: "image.loading.queue", qos: .userInitiated)
|
||||
|
||||
init() {
|
||||
cache.totalCostLimit = 50 * 1024 * 1024
|
||||
|
||||
Reference in New Issue
Block a user