Как можно отключить выбор в PDFView?

Отображение PDFDocument в PDFView позволяет пользователю выбирать части документа и выполнять действия, например, "копировать" с выделением. Как можно отключить выбор в PDFView, сохраняя при этом возможность для пользователя увеличивать и уменьшать масштаб и прокручивать PDF?

PDFView само по себе, кажется, не предлагает такую ​​собственность, а PDFViewDelegate,

6 ответов

Решение

Вы должны создать подкласс PDFView, как таковой:

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

}

Просто нужно сделать, это автоматически очистит выделение, и пользователь больше не будет долго нажимать на текст PDF.

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

}

Это ниже 2 строк необходимо добавить в canPerformAction()

self.currentSelection = nil
self.clearSelection()

Для iOS 13 вышеуказанное решение больше не работает. Похоже, они изменили внутреннюю реализациюPDFViewи, в частности, как настроены распознаватели жестов. Я думаю, что вообще не рекомендуется делать такие вещи, но это все еще можно сделать без использования какого-либо внутреннего API, вот как:

1) Рекурсивно собрать все подвиды PDFView (для этого см. вспомогательную функцию ниже)

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

2) Перебирайте их и деактивируйте любые UILongPressGestureRecognizers:

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

Вспомогательная функция для рекурсивного получения всех подвидов заданного типа:

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
}

Я вызываю приведенный выше код из viewDidLoad метод содержащего контроллера представления.

Я еще не нашел хорошего способа превратить это в подкласс PDFView, который был бы предпочтительным способом повторного использования и мог бы быть просто дополнением к вышеуказанному NonSelectablePDFView. То, что я пробовал до сих пор, отменяетdidAddSubview и добавив приведенный выше код после вызова super, но это не сработало, как ожидалось. Похоже, что распознаватели жестов добавляются только на более позднем этапе, поэтому следующим шагом будет выяснение того, когда это произойдет и есть ли способ для подкласса вызвать некоторый пользовательский код после того, как это произошло.

Вы можете решить свою проблему, переопределив addGestureRecognizer(_:) метод и canPerformAction(_:withSender:) метод в PDFView подкласс.

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
    }

}

В качестве альтернативы предыдущей реализации вы можете просто переключить UILongPressGestureRecognizerisEnabled собственность на false в инициализаторе.

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
    }

}

В моем тесте двойное нажатие и «нажмите, а затем перетащите» не активируют выбор на iOS16, но на iOS13 это будет, так что вот так:

  1. Расширьте, добавьте функцию отключения выделения текста. я добавляю этоUIViewControllerрасширение, поэтому при вызове мне не нужно звонитьpdfView. recursivelyDisableSelection(view: pdfView), толькоrecursivelyDisableSelection(view: pdfView)казалось бы немного лучше.
      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. При вызове функции передайте экземплярPDFViewвы хотите отключить выделение текста. Примечание: его следует вызывать ПОСЛЕ установки документа PDFView, иначе он не будет работать.

  2. Предупреждение. Если вы включили usePageViewController() для вашего PDFView, кажется, что при прокрутке для изменения текущей страницы жесты будут добавлены снова. Разумной мыслью было бы добавить наблюдателя дляPDFViewPageChangedсообщение, я пробовал, работает только иногда. Лучше всего прослушать уведомление, так оно всегда работает. Опять же, наблюдателя следует добавлять после установки документа PDFView, иначе он не будет работать.

Итак, если вы, как и я, используете pageViewController, вам следует вызывать его в двух местах: один после установки документа, это отключение выбора для первой PDFPage, другой вPDFViewVisiblePagesChangedуведомление для последующей прокрутки страницы.

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

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

Позже в контроллере представления определите функцию-оболочку объекта c:

      @objc func pageChanged() {
    recursivelyDisableSelection(view: pdfView)
}

Следует отметить, что этого недостаточно для отключения выделения текста, поскольку существует также UITapAndHalfRecognizer - очевидно, частный класс Apple, - который также создает выделения.

Он прикреплен к PDFDocumentView, который является еще одной частной деталью реализации PDFView, и которую вы не можете заменить своей собственной реализацией класса.

Другие вопросы по тегам