Reputation: 3443
Does PDFKit on iOS expose a PDFView's underlying UIScrollView or is there any other way to directly detect that the user has scrolled a PDFView?
My use case is to hide a nav bar when the document is scrolled so as a workaround I've added my own pan gesture recogniser to the PDFView's parent and I do the hiding in gestureRecognizerShouldBegin and always return false but I expect there's something more like UIScrollViewDelegate that I'm missing in the docs.
Upvotes: 9
Views: 6197
Reputation: 1
try this!
(pdfView.subviews[0] as? UIScrollView)?.delegate = self
and observe the scrollview delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 0 {
/// ...
} else {
/// ...
}
}
Upvotes: 0
Reputation: 1718
Decided on a solution other's haven't done yet. Went with key value observing on the contentOffset
property of the underlying UIScrollView
.
You can use this extension to run a callback every time the scroll offset changes.
var observation = pdfView.onScrollOffsetChanged { scroll in
print("PDFView scrolled to \(scroll.contentOffset).")
}
The extension
extension PDFView {
func onScrollOffsetChange(handler: @escaping (UIScrollView) -> Void) -> NSKeyValueObservation? {
detectScrollView()?.observe(\.contentOffset) { scroll, _ in
handler(scroll)
}
}
private func detectScrollView() -> UIScrollView? {
for view in subviews {
if let scroll = view as? UIScrollView {
return scroll
} else {
for subview in view.subviews {
if let scroll = subview as? UIScrollView {
return scroll
}
}
}
}
print("Unable to find a scrollView subview on a PDFView.")
return nil
}
}
Upvotes: 3
Reputation: 9061
Does PDFKit on iOS expose a PDFView's underlying UIScrollView
No, but hopefully Apple will add this in the future. I remember that UIWebView didn't have it originally and it was added later.
or is there any other way to directly detect that the user has scrolled a PDFView
No, it looks like none of the notifications provided by PDFViewDelegate
address this.
I'm migrating from UIWebView to PDFView and am using scrollViewDidScroll
for a bunch of stuff, so I didn't want to rely on just adding a pan gesture recognizer. Building from @Matthijs's answer, I'm finding the UIScrollView inside the PDFView, making my class its delegate, then passing any events back to the scroll view (which was its own delegate before my class became the delegate) so it can respond to them, too. With UIWebView, this last step was not necessary, but with PDFView, zooming and possibly other functions won't work without it.
I'm overriding all the documented delegate methods to reduce the chance that this will break if Apple changes the internal function of PDFView. However, I had to check respondsToSelector
in each method, because the original scroll view delegate doesn't currently implement all of them.
- (void)viewDidLoad {
// create the PDFView and find its inner scrollView
self.pdfView = [[PDFView alloc] init];
for (UIView *subview in self.pdfView.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
self.scrollView = (UIScrollView *)subview;
} else {
for (UIView *subsubview in subview.subviews) {
if ([subsubview isKindOfClass:[UIScrollView class]]) {
self.scrollView = (UIScrollView *)subsubview;
}
}
}
}
}
- (void)loadPDFDocument:(NSString *)URL {
// load a document, then become the delegate for the scrollView (we have to do that after loading the document)
PDFDocument *document = [[PDFDocument alloc] initWithURL:URL];
self.pdfView.document = document;
self.scrollView.delegate = self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// *** respond to scroll events here ***
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
[scrollViewDelegate scrollViewDidScroll:scrollView];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
[scrollViewDelegate scrollViewWillBeginDragging:scrollView];
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[scrollViewDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) {
[scrollViewDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) {
return [scrollViewDelegate scrollViewShouldScrollToTop:scrollView];
}
return TRUE;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidScrollToTop:)]) {
[scrollViewDelegate scrollViewDidScrollToTop:scrollView];
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)]) {
[scrollViewDelegate scrollViewWillBeginDecelerating:scrollView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
[scrollViewDelegate scrollViewDidEndDecelerating:scrollView];
}
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
return [scrollViewDelegate viewForZoomingInScrollView:scrollView];
}
return nil;
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
[scrollViewDelegate scrollViewWillBeginZooming:scrollView withView:view];
}
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) {
[scrollViewDelegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
}
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidZoom:)]) {
[scrollViewDelegate scrollViewDidZoom:scrollView];
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
[scrollViewDelegate scrollViewDidEndScrollingAnimation:scrollView];
}
}
- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView {
UIScrollView <UIScrollViewDelegate> *scrollViewDelegate = (UIScrollView <UIScrollViewDelegate> *)self.scrollView;
if ([scrollViewDelegate respondsToSelector:@selector(scrollViewDidChangeAdjustedContentInset:)]) {
[scrollViewDelegate scrollViewDidChangeAdjustedContentInset:scrollView];
}
}
Upvotes: 4
Reputation: 535
I did this to detect zooming and panning on a pdfView to copy those gestures to a second pdfView, and it's working perfectly fine here. Got some help to detect vertical and horizontal panning by the PanDirectionGestureRecognizer I found here: stackoverflow.com/a/55635482/558112
class Document: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Subscribe to notifications.
NotificationCenter.default.addObserver(self, selector: #selector(onPageZoomAndPan), name: .PDFViewScaleChanged, object: pdfView
// get the scrollView in pdfView and attach gesture recognizers
outerLoop: for subView in pdfView.subviews {
for view in subView.subviews {
if let scrollView = view as? UIScrollView {
let xScrollViewPanGesture = PanDirectionGestureRecognizer(direction: .horizontal, target: self, action: #selector(onPageZoomAndPan))
xScrollViewPanGesture.delegate = self
scrollView.addGestureRecognizer(xScrollViewPanGesture)
let yScrollViewPanGesture = PanDirectionGestureRecognizer(direction: .vertical, target: self, action: #selector(onPageZoomAndPan))
yScrollViewPanGesture.delegate = self
scrollView.addGestureRecognizer(yScrollViewPanGesture)
break outerLoop
}
}
}
}
// MARK: - UIScrollViewDelegate
@objc private func onPageZoomAndPan() {
let rect = pdfView.convert(pdfView.bounds, to: pdfView.currentPage!)
pdfViewSecondScreen.scaleFactor = pdfView.scaleFactor
pdfViewSecondScreen.go(to: rect, on: pdfView.currentPage!)
}
}
enum PanDirection {
case vertical
case horizontal
}
// UIKit.UIGestureRecognizerSubclass
import UIKit.UIGestureRecognizerSubclass
class PanDirectionGestureRecognizer: UIPanGestureRecognizer {
let direction : PanDirection
init(direction: PanDirection, target: AnyObject, action: Selector) {
self.direction = direction
super.init(target: target, action: action)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
if state == .began {
let vel = velocity(in: self.view!)
switch direction {
case .horizontal where abs(vel.y) > abs(vel.x):
state = .cancelled
case .vertical where abs(vel.x) > abs(vel.y):
state = .cancelled
default:
break
}
}
}
}
Upvotes: 3
Reputation: 1442
Try this,
NotificationCenter.default.addObserver(self, selector: #selector(handlePageChange(notification:)), name: Notification.Name.PDFViewPageChanged, object: nil)
@objc private func handlePageChange(notification: Notification)
{
print("Page changed")
}
Upvotes: 5