How to resolve the conflict of gestures between PDFView and SCrollView [Swift5, iOS 14]
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.
iOS PDFKit adjust pdf frame in PDFView
New Hack:
So it turns out, after digging through some more private APIs (yes, this solution is still gross) that there is this magical property that I had completely overlooked on PDFScrollView
(a private, internal view) called... /p>
forcesTopAlignment
/p>
To enable this, find the PDFScrollView
in your PDFView
and:
pdfScrollView.setValue(true, forKey: "forcesTopAlignment")
That does the trick!
Old Hack:
After a few days up against the wall I finally figured out a way to get PDFView
to behave how I want.
Disclosure: This solution uses method swizzling and relies on private view hierarchy and could break at any time. That said, it works for now and I'm happy with the solution.
The full solution is available in this gist. (The meat is in overrideCenterAlign
.)
There is a private method aptly named _centerAlign
which vertically centers and scales the pdf pages as they come onto the screen and as they're scaled with the pinch gesture. Swizzling that method allows me to inject my own logic and apply my own transforms to position the pdf view how I'd like (with the top of the page aligned to the top of the view.)
There are two cases to consider. The "short page" case (pages 1, 3, 4 in the example) and the "tall page" case (page 2 in the example.) In both cases I start by invoking the original implementation of _centerAlign
so that it can apply its transforms and manage updating the internal scroll view.
- For the "short page" case, I apply the same transform with a vertical translation to align the top of the pdf with the top of the view.
- For the "long page" case, I adjust the internal scroll view's zoom scale as it comes onto the screen so that it's scaled to fit the width of the view.
All of that said, I'm open to cleaner solutions that don't rely on private methods and view hierarchy. If you know of a better way to accomplish this, please post an answer!
iOS PDFKit Disable vertical scroll bounce
Unfortunately, there isn't an exported API to set the PDFView
desired bouncing behavior.
Having said that, you can (safely) exploit a PDFView
implementation detail to hack your way around it for now:
extension PDFView {
/// Disables the PDFView default bouncing behavior.
func disableBouncing() {
for subview in subviews {
if let scrollView = subview as? UIScrollView {
scrollView.bounces = false
return
}
}
print("PDFView.disableBouncing: FAILED!")
}
}
and then use it like this in your code:
pdfView.disableBouncing()
Caveat. Please keep in mind that such solution might break in future iOS releases. Nevertheless, rest assured your app won't crash as a result (you only won't be disabling the bouncing behavior at all).
Related Topics
Custom Radix Columns (+Special Characters)
How to Override Setter in Swift
How to Print Http Request to Console
"'Init' Is Deprecated" Warning After Swift4 Convert
Error "[Sharesheet] Connection Invalidated" Error iOS13+ But Not on iOS 11.4
Set Multiple Arrow Directions on UIpopovercontroller in Swift
Can't Get Throws to Work with Function with Completion Handler
How to Update UIviewrepresentable with Observableobject
What Was The Reason for Swift Assignment Evaluation to Void
When How to Start Submitting Apps to The iOS App Store Written Using The Swift Programming Language
Check Os Version Using Swift on MAC Os X
Create an Array of Protocols with Constrained Associated Types
Is There a Way in Swiftui to Detect If a User Has Larger Text Size Enabled
Accessing Bundle of Main Application While Running Xctests
Access Id Does Not Work When Testing a Textfield with 'Issecuretextentry = True'