How can selection be disabled in PDFView?
Asked Answered
U

6

9

Displaying a PDFDocument in a PDFView allows the user to select parts of the document and perform actions e.g. "copy" with the selection. How can selection be disabled in a PDFView while preserving the possibility for the user to zoom in and out and scroll in the PDF?

PDFView itself does not seem to offer such a property nor does the PDFViewDelegate.

Uturn answered 4/3, 2018 at 19:28 Comment(0)
F
7

You have to subclass PDFView, as such:

class MyPDFView: PDFView {

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return false
    }

    override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
        if gestureRecognizer is UILongPressGestureRecognizer {
            gestureRecognizer.isEnabled = false
        }

        super.addGestureRecognizer(gestureRecognizer)
    }

}
Formyl answered 4/3, 2018 at 20:2 Comment(0)
H
4

Just need to do is it will auto clear the selection and User will no longer long-press on PDF text.

class MyPDFView: PDFView {

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        self.currentSelection = nil
        self.clearSelection()

        return false
    }

    override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
        if gestureRecognizer is UILongPressGestureRecognizer {
            gestureRecognizer.isEnabled = false
        }

        super.addGestureRecognizer(gestureRecognizer)
    }

}

This below 2 lines need to add in canPerformAction()

self.currentSelection = nil
self.clearSelection()
Hobbie answered 24/2, 2020 at 13:44 Comment(0)
M
3

For iOS 13, the above solution no longer works. It looks like they've changed the internal implementation of PDFView and specifically how the gesture recognizers are set up. I think generally it's discouraged to do this kind of thing, but it can still be done without using any internal API, here's how:

1) Recursively gather all subviews of PDFView (see below for the helper function to do this)

let allSubviews = pdfView.allSubViewsOf(type: UIView.self)

2) Iterate over them and deactivate any UILongPressGestureRecognizers:

for gestureRec in allSubviews.compactMap({ $0.gestureRecognizers }).flatMap({ $0 }) {
    if gestureRec is UILongPressGestureRecognizer {
        gestureRec.isEnabled = false
    }
}

Helper func to recursively get all subviews of a given type:

func allSubViewsOf<T: UIView>(type: T.Type) -> [T] {
    var all: [T] = []
    func getSubview(view: UIView) {
        if let aView = view as? T {
            all.append(aView)
        }
        guard view.subviews.count > 0 else { return }
        view.subviews.forEach{ getSubview(view: $0) }
    }
    getSubview(view: self)
    return all
}

I'm calling the above code from the viewDidLoad method of the containing view controller.

I haven't yet found a good way to work this into a subclass of PDFView, which would be the preferred way for reusability and could just be an addition to the above NonSelectablePDFView. What I've tried so far is overriding didAddSubview and adding the above code after the call to super, but that didn't work as expected. It seems like the gesture recognizers are only being added at a later step, so figuring out when that is and if there's a way for the subclass to call some custom code after this happened would be a next step here.

Mudlark answered 1/10, 2019 at 14:43 Comment(1)
This disables the long press edit but not the double tap and hold. Add: type(of: gestureRec).description() == "UITapAndAHalfRecognizer" to the if in 2) above.Granulite
F
1

With Swift 5 and iOS 12.3, you can solve your problem by overriding addGestureRecognizer(_:) method and canPerformAction(_:withSender:) method in a PDFView subclass.

import UIKit
import PDFKit

class NonSelectablePDFView: PDFView {

    override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
        (gestureRecognizer as? UILongPressGestureRecognizer)?.isEnabled = false
        super.addGestureRecognizer(gestureRecognizer)
    }

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return false
    }

}

As an alternative to the previous implementation, you can simply toggle UILongPressGestureRecognizer isEnabled property to false in the initializer.

import UIKit
import PDFKit

class NonSelectablePDFView: PDFView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        if let gestureRecognizers = gestureRecognizers {
            for gestureRecognizer in gestureRecognizers where gestureRecognizer is UILongPressGestureRecognizer {
                gestureRecognizer.isEnabled = false
            }
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        return false
    }

}
Fredi answered 11/3, 2018 at 21:34 Comment(1)
it looks like this answer is not working anymore in iOS13, Apple did change something even with this code is still selected text ! , on iOS12 and below it was working fine !Clouet
E
1

You should note that this is not sufficient to disable text selecting, as there is a UITapAndHalfRecognizer as well – obviously a private Apple class - that also creates selections.

It is attached to the PDFDocumentView, which is another private implementation detail of PDFView, and which you can not replace with your own class implementation.

Ediva answered 17/1, 2020 at 11:52 Comment(0)
P
1

In my test, double tap and "tap then drag" won't enable selection on iOS16, but for iOS13 it will, so here goes:

  1. Extend UIViewController, add a function to disable text selection. I'm throwing it in UIViewController's extension so when calling it, I don't need to call pdfView. recursivelyDisableSelection(view: pdfView), just recursivelyDisableSelection(view: pdfView) would seem a little better.
import UIKit
import PDFKit
extension UIViewController {

    func recursivelyDisableSelection(view: UIView) {
        
        // Get all recognizers for the PDFView's subviews. Here we are ignoring the recognizers for the PDFView itself, since we know from testing that not the reason for the mess.
        for rec in view.subviews.compactMap({$0.gestureRecognizers}).flatMap({$0}) {
            // UITapAndAHalfRecognizer is for a gesture like "tap first, then tap again and drag", this gesture also enable's text selection
            if rec is UILongPressGestureRecognizer || type(of: rec).description() == "UITapAndAHalfRecognizer" {
                rec.isEnabled = false
            }
        }
        
        // For all subviews, if they do have subview in itself, disable the above 2 gestures as well.
        for view in view.subviews {
            if !view.subviews.isEmpty {
                recursivelyDisableSelection(view: view)
            }
        }
    }
}
  1. When calling the function, pass the instance of PDFView you wanna disable text selection. Notice: it should be called AFTER PDFView's document is set, otherwise it won't work.

  2. Warning: If you enabled usePageViewController() for your PDFView, it seems when scrolling to change current page, gestures will be added again. A reasonable thought would be adding an observer for PDFViewPageChanged message, I tried, it only works sometimes. A better notification message to listen to is PDFViewVisiblePagesChanged, this way it always works. Again, observer should be added after PDFView's document has been set otherwise it won't work.

So if you are using pageViewController like me, you should call this in 2 places: One after whenever document is set, this disable selection for the first PDFPage, another in PDFViewVisiblePagesChanged notification, for following page scrolling.

pdfView.document = `YOUR_PDF_DOCUMENT`
recursivelyDisableSelection(view: pdfView)

NotificationCenter.default.addObserver(self, selector: #selector(pageChanged), name: .PDFViewVisiblePagesChanged, object: nil)

Later in your view controller, define the objective c wrapper function:

@objc func pageChanged() {
    recursivelyDisableSelection(view: pdfView)
}
Porfirioporgy answered 25/5, 2023 at 4:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.