0

I have a view, which contains PDFView from PDFKit. I want to see 2 full pages at once on my screen and they need to fit both height and width. I can see 2 ways of doing it, but neither is perfect:

Option 1) Display mode with .twoUpContinuous - here it fits width, but height is cropped in ~2/3 of the PDF.

lazy var pdfView: PDFView = {
    let view = PDFView().layoutable()
    view.displayMode = .twoUpContinuous // !
    view.displayDirection = .vertical
    view.minScaleFactor = view.scaleFactorForSizeToFit
    view.maxScaleFactor = 3
    view.autoScales = true
    return view
}()

Option 2) Display mode with .twoUp - here it fits width and height, but gesture recognizer doesn't record swipe down, and as a result user cannot change page to 3 and 4.

lazy var pdfView: PDFView = {
    let view = PDFView().layoutable()
    view.displayMode = .twoUp // !
    view.displayDirection = .vertical
    view.minScaleFactor = view.scaleFactorForSizeToFit
    view.maxScaleFactor = 3
    view.autoScales = true
    return view
}()

Is there any built-in solution for such case..? Or do I have to add manually swipe gesture recognizer and change pages based on that with .twoUp solution..?

Nat
  • 12,032
  • 9
  • 56
  • 103
  • It seems like there is no built in solution. Another thought I could think of is loading the pages in a `collectionview` - I am not sure which is more work - implementing your own recognizer or going the collectionview route which has the gestures but you need to draw the pdfpage. – Shawn Frank Feb 23 '22 at 08:10
  • @ShawnFrank Interesting idea, although I'd be afraid about the pressure of such solution. I added my own answer with 3 possible solutions, and I'll end up probably with the 3rd one. – Nat Feb 23 '22 at 09:54

1 Answers1

1

So in the end I thought of 3 solutions, neither of them is perfect. I like solution 3 the most.


SOLUTION 1: not stable, is failing to render pdf from time to time or launches not on the 1st page. But most often works fine.

lazy var pdfView: PDFView = {
    let view = PDFView().layoutable()
    view.displayMode = .twoUpContinuous
    view.autoScales = false
    view.displayDirection = .vertical
    return view
}()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    updatePDFViewScaling()
}
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    updatePDFViewScaling()
}
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    updatePDFViewScaling()
}
private func updatePDFViewScaling() {
    customView.pdfView.displayMode = .twoUp
    let scaleFactor = customView.pdfView.scaleFactorForSizeToFit
    customView.pdfView.displayMode = .twoUpContinuous
    customView.pdfView.scaleFactor = scaleFactor
    customView.pdfView.minScaleFactor = scaleFactor
    customView.pdfView.maxScaleFactor = 3
}

SOLUTION 2: horizontally perfect, vertically not. Width of 2 PDFs fits, however the bottom is cropped. In case of A4 documents, that should crop not too much on most iPads, because all of them have the aspect ratio of 3:4.

lazy var pdfView: PDFView = {
    let view = PDFView().layoutable()
    view.displayMode = .twoUpContinuous
    view.displayDirection = .vertical
    view.minScaleFactor = view.scaleFactorForSizeToFit
    view.maxScaleFactor = 3
    return view
}()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    customView.pdfView.autoScales = true
}
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    customView.pdfView.autoScales = true
}
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    customView.pdfView.autoScales = true
}

SOLUTION 3: There is no animation when you scroll with your finger to the next/previous page INSIDE PDF.

lazy var pdfView: PDFView = {
    let view = PDFView().layoutable()
    view.displayMode = .twoUp
    view.displayDirection = .vertical
    view.minScaleFactor = view.scaleFactorForSizeToFit
    view.maxScaleFactor = 3
    view.autoScales = true
    return view
}()
// somewhere in view's init or wherever you want
    let upGestureRegocnizer = UISwipeGestureRecognizer(target: self, action: #selector(upSwipeAction(gestureRegonizer:)))
    upGestureRegocnizer.direction = .up
    pdfView.addGestureRecognizer(upGestureRegocnizer)
    let downGestureRegocnizer = UISwipeGestureRecognizer(target: self, action: #selector(downSwipeAction(gestureRegonizer:)))
    downGestureRegocnizer.direction = .down
    pdfView.addGestureRecognizer(downGestureRegocnizer)

@objc private func upSwipeAction(gestureRegonizer: UISwipeGestureRecognizer) {
    guard pdfView.canGoToNextPage else { return }
    pdfView.goToNextPage(self)
}
@objc private func downSwipeAction(gestureRegonizer: UISwipeGestureRecognizer) {
    guard pdfView.canGoToPreviousPage else { return }
    pdfView.goToPreviousPage(self)
}
Nat
  • 12,032
  • 9
  • 56
  • 103
  • 1
    Nice ! I'm surprised PDKKit does not have any scroll with animation functions. I think one small correction for the gesture actions should be `@objc private` rather than `private @objc` ? – Shawn Frank Feb 23 '22 at 10:47
  • Oh indeed, thanks for noticing, fixed! I have that in the private extension, so just quickly added it when pasting here. It's curious why Apple didn't give any animated options here. Obviously also, `UIViewPropertyAnimator`/ `UIView.animate` / `CoreAnimation` can't help with this. So - it's pretty blocked. – Nat Feb 23 '22 at 15:17