【问题标题】:Detect that a PDFView did scroll检测到 PDFView 确实滚动
【发布时间】:2018-06-19 11:21:26
【问题描述】:

iOS 上的 PDFKit 是否公开了 PDFView 的底层 UIScrollView,或者有没有其他方法可以直接检测到用户滚动了 PDFView?

我的用例是在文档滚动时隐藏导航栏,作为一种解决方法,我将自己的平移手势识别器添加到 PDFView 的父级,我在gestureRecognizerShouldBegin 中进行隐藏并始终返回false,但我希望有一些东西更像是我在文档中缺少的 UIScrollViewDelegate。

【问题讨论】:

  • 一个 hacky 解决方案:PDFView 有一个继承自 UIScrollViewPDFScrollView 子视图(私有类),因此您可以强制转换它并将 UIScrollViewDelegate 设置为它。我做了一个快速设置,它没有设置委托,但你应该检查以防它可能会破坏事情。但是,这些解决方案当然是不安全的,因为 Apple 可能会改变未来的工作方式。

标签: ios cocoa-touch ios-pdfkit


【解决方案1】:

决定了其他人尚未完成的解决方案。对底层UIScrollViewcontentOffset 属性进行键值观察。

您可以使用此扩展程序在每次滚动偏移量更改时运行回调。

var observation = pdfView.onScrollOffsetChanged { scroll in
    print("PDFView scrolled to \(scroll.contentOffset).")
}

扩展

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
    }
}

【讨论】:

    【解决方案2】:

    iOS 上的 PDFKit 是否公开了 PDFView 的底层 UIScrollView

    不,但希望 Apple 将来会添加此功能。记得UIWebView原来没有,后来加的。

    或者有没有其他方法可以直接检测到用户滚动了 PDFView

    不,PDFViewDelegate 提供的通知似乎都没有解决这个问题。

    我正在从 UIWebView 迁移到 PDFView 并且正在使用 scrollViewDidScroll 来处理一堆东西,所以我不想仅仅依靠添加一个平移手势识别器。从@Matthijs 的答案构建,我在 PDFView 中找到 UIScrollView,使我的类成为它的委托,然后将任何事件传递回滚动视图(在我的类成为委托之前它是它自己的委托),以便它可以响应它们, 也。使用 UIWebView,这最后一步不是必需的,但使用 PDFView,缩放和可能的其他功能将无法正常工作。

    我将覆盖所有记录在案的委托方法,以减少如果 Apple 更改 PDFView 的内部功能而中断的可能性。但是,我必须在每个方法中检查respondsToSelector,因为原始滚动视图委托目前并未实现所有这些。

    - (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];
        }
    }
    

    【讨论】:

    • 你能告诉我如何捕捉到何时选择注释。我使用了 PDFViewAnnotationWillHit 通知,但没有运气。
    • 抱歉,我根本没有使用过注释功能。
    【解决方案3】:

    我这样做是为了检测 pdfView 上的缩放和平移,以将这些手势复制到第二个 pdfView,它在这里工作得非常好。 通过我在这里找到的 PanDirectionGestureRecognizer 获得了一些帮助来检测垂直和水平平移: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
                }
            }
        }
    } 
    

    【讨论】:

    • 当我尝试这个时,UIScrollView 处于子视图的第一级,所以我修改了这段代码来检查每个子视图,然后在需要时检查每个子视图的子视图。
    【解决方案4】:

    试试这个,

    NotificationCenter.default.addObserver(self, selector: #selector(handlePageChange(notification:)), name: Notification.Name.PDFViewPageChanged, object: nil)
    
    @objc private func handlePageChange(notification: Notification)
    {
        print("Page changed")
    }
    

    【讨论】:

    • 无论情况如何,最好提供一个更通用的解决方案,检查哪个滚动视图发送了通知;我们不知道屏幕上同时有多少滚动视图可能还会发布通知。
    • 我试图获取滚动视图委托以进行 pdfview 滚动,但无法获取。以上解决方案对我有帮助。如果您能够获得 PDFView 的委托,请在此处分享该解决方案。
    • 您不需要代理...通知包含有关发件人的信息。
    • 这个解决方案没有回答这个问题,即确定滚动视图何时发生变化,而不是新页面何时在视图中占主导地位。
    • @Matthijs,问题 - 他需要在滚动 PDFView 时隐藏导航栏,所以我建议了这个解决方案。如果您在 PDFView 中寻找页码更改,那么您可以在通知功能中使用它 ==> YourPDFView。当前页面。感谢您的评论。
    猜你喜欢
    • 2020-02-11
    • 1970-01-01
    • 2020-08-31
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    • 2012-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多