I am creating a PDF viewer for iPads, with which users can read a PDF by scrolling horizontally.
I created the following code to implement the single page view with page change with gestures (while consulting with How to create a single page vertical scrolling PDFView in Swift and elsewhere).
Although this approach works fine most of the time, I realized that gestures are not detected (or called) when a PDF file is zoomed in. Because of this, I cannot go to the next page by swiping the screen. Playing with the extension PDFView {}
function I created, I found out that disabling the user interaction in subview
enables me to detect the swipe gestures. However, now I cannot scroll the page inside the PDFView
. I would appreciate it if you could help me figure out how to fix this.
What I would like to implement is something like PDF Expert
(https://apps.apple.com/us/app/pdf-expert-pdf-reader-editor/id743974925), where I can scroll over to the next page horizontally.
Thank very much you for your help in advance!
import UIKit
import PDFKit
//PDF Zoom scale
var scaleOfPdf: CGFloat = 4
extension PDFView {
func disableBouncing(){
for subview in subviews{
if let scrollView = subview as? UIScrollView{
scrollView.bounces = false
return
}
}
class ViewController: UIViewController, UIGestureRecognizerDelegate, UIDocumentPickerDelegate {
@IBOutlet weak var pdfView: PDFView!
override func viewDidLoad(){
super.viewDidLoad()
pdfView.autoresizesSubviews = true
pdfView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleTopMargin, .flexibleLeftMargin]
pdfView.displayDirection = .horizontal
pdfView.displayMode = .singlePage
pdfView.autoScales = true
// setting a color for background
pdfView.backgroundColor = .black
pdfView.document = pdfDocument
// pdfView.usePageViewController(true, withViewOptions: [UIPageViewController.OptionsKey.interPageSpacing: 20])
pdfView.maxScaleFactor = 4.0
pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
pdfView.disableBouncing()
//setting swipe gesture
let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(respondLeftSwipeGesture(_:)))
leftSwipeGesture.direction = [UISwipeGestureRecognizer.Direction.left]
pdfView.addGestureRecognizer(leftSwipeGesture)
let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(respondRightSwipeGesture(_:)))
rightSwipeGesture.direction = [UISwipeGestureRecognizer.Direction.right]
pdfView.addGestureRecognizer(rightSwipeGesture)
}
//setting swipe-gesture
@objc func respondLeftSwipeGesture(_ sender: UISwipeGestureRecognizer) {
print("left swipe was detected")
if pdfView.document == nil { return }
scaleOfPdf = pdfView.scaleFactor
pdfView.goToNextPage(self)
pdfView.scaleFactor = scaleOfPdf
}
@objc func respondRightSwipeGesture(_ sender: UISwipeGestureRecognizer) {
print("right swipe was detected")
if pdfView.document == nil { return }
scaleOfPdf = pdfView.scaleFactor
pdfView.goToPreviousPage(self)
pdfView.scaleFactor = scaleOfPdf
}
}
Gesture Recognizers are working as a chain or pipeline that processes touches - after one (G1) fails, second one (G2) tries to recognize its gesture. Here you have at least 4 recognizers - your 2 ones (left
and right
), and the 2 scrollView's ones (pan
and pinch
). I will give the brief solution that covers only scrollView's pan
recognizer, if you'll see problems also with pinch
- you'll need to follow the same approach.
Let's say G1 is your left
recognizer, and G2
is scrollView's pan
recognizer.
In order to make G2 process the same touches as G1, they should be told to recognize simultaneously.
Also, the user might move his/her finger a bit horizontally while scrolling vertically, so in that case, you also want scrolling to start only after your G1 gives up on swipe and fails to recognize it.
In order to achieve that, you should add this code to your VC.
override func viewDidLoad(){
super.viewDidLoad()
...
leftSwipeGesture.delegate = self
leftSwipeGesture.cancelsTouchesInView = false
rightSwipeGesture.delegate = self
rightSwipeGesture.cancelsTouchesInView = false
}
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer == leftSwipeGesture
|| gestureRecognizer == rightSwipeGesture
|| otherGestureRecognizer == leftSwipeGesture
|| otherGestureRecognizer == rightSwipeGesture
}
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
guard let _ = gestureRecognizer as? UIPanGestureRecognizer else { return false }
return otherGestureRecognizer == leftSwipeGesture
|| otherGestureRecognizer == rightSwipeGesture
}
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
guard let _ = otherGestureRecognizer as? UIPanGestureRecognizer else { return false }
return gestureRecognizer == leftSwipeGesture
|| gestureRecognizer == rightSwipeGesture
}
If UIGestureRecognizerDelegate
methods that I added are not getting called, you'll need to create a subclass PDFView
, make left/rightSwipeGesture.delegate = pdfView
and override in your PDFView
subclass its UIGestureRecognizerDelegate
methods with this logic.