ios – The way to Disable Textual content Choice and Modifying in QLPreviewController for PDF and Textual content Information in Swift?

ios – The way to Disable Textual content Choice and Modifying in QLPreviewController for PDF and Textual content Information in Swift?


I am making an attempt to dam textual content choice and modifying in a QLPreviewController displaying PDF and textual content recordsdata in a Swift app.

I’ve had success utilizing technique swizzling on buildMenu for UIApplication and UIResponder, which works successfully on customary textual content fields. Nonetheless, with QLPreviewController, my makes an attempt to intercept or modify the UIMenu calls haven’t succeeded.

The QLPreviewController class solely has strategies associated to opening and displaying QLPreviewItem, however doesn’t expose any clear API for dealing with or disabling the UIMenu. Moreover, QLPreviewItem itself has no strategies that I might use to affect choice habits.

Any concepts on methods to stop textual content choice in QLPreviewController for these file sorts?

Code for instance:

import UIKit
import QuickLook

class ViewController: UIViewController {

    var previewItems: [URL] = []

    override func viewDidLoad() {
        tremendous.viewDidLoad()

        setupViews()
        createMultiPagePDF()
        createMultiPageTextFileWithBlankLines()

        UIResponder.swizzle()
        UIApplication.swizzleAction()
    }

    @objc personal func openPreview() {
        guard !previewItems.isEmpty else {
            print("Not recordsdata for previews")
            return
        }

        let previewController = CustomQLPreviewController()
        previewController.dataSource = self
        previewController.delegate = previewController
        current(previewController, animated: true, completion: nil)
    }
}

personal extension ViewController {

    func setupViews() {
        let previewButton = UIButton(kind: .system)
        previewButton.setTitle("Open preview", for: .regular)
        previewButton.titleLabel?.font = UIFont.systemFont(ofSize: 20)
        previewButton.translatesAutoresizingMaskIntoConstraints = false
        previewButton.addTarget(self, motion: #selector(openPreview), for: .touchUpInside)

        view.addSubview(previewButton)

        NSLayoutConstraint.activate([
            previewButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            previewButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])

        let previewTextField = UITextField()
        previewTextField.font = UIFont.systemFont(ofSize: 12)
        previewTextField.textColor = .black
        previewTextField.borderStyle = .roundedRect
        previewTextField.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(previewTextField)

        NSLayoutConstraint.activate([
            previewTextField.topAnchor.constraint(equalTo: previewButton.bottomAnchor, constant: 50),
            previewTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }

    func createMultiPagePDF() {
        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, CGRect(x: 0, y: 0, width: 200, top: 200), nil)

        let pdfTexts = [
            "It's first page PDF for testing",
            "It's second page PDF for testing",
            "It's third page PDF for testing"
        ]

        for textual content in pdfTexts {
            UIGraphicsBeginPDFPage()

            let textRect = CGRect(x: 20, y: 20, width: 160, top: 160)
            textual content.draw(in: textRect, withAttributes: [.font: UIFont.systemFont(ofSize: 18)])
        }

        UIGraphicsEndPDFContext()

        if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
            let pdfURL = documentsDirectory.appendingPathComponent("multiPageTestFile.pdf")
            pdfData.write(to: pdfURL, atomically: true)
            previewItems.append(pdfURL)
        }
    }

    func createMultiPageTextFileWithBlankLines() {
        let pageContents = [
            "It's first page PDF for testing",
            "It's second page PDF for testing",
            "It's third page PDF for testing",
            "It's fourth page PDF for testing"
        ]

        var fullText = ""
        for (index, pageText) in pageContents.enumerated() {
            fullText += "=== Web page quantity (index + 1) ===n"
            fullText += pageText
            fullText += "n" + String(repeating: "n", rely: 50)
        }

        if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
            let textFileURL = documentsDirectory.appendingPathComponent("multiPageTestFile_BlankLines.txt")
            do {
                attempt fullText.write(to: textFileURL, atomically: true, encoding: .utf8)
                previewItems.append(textFileURL)
            } catch {
                print("Error: (error.localizedDescription)")
            }
        }
    }
}

extension ViewController: QLPreviewControllerDataSource {

    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
        return previewItems.rely
    }

    func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
        return previewItems[index] as QLPreviewItem
    }
}

extension UIResponder {

    static func swizzle() {

        guard #out there(iOS 13.0, *) else {
            return
        }

        guard
            let originalMethod = class_getInstanceMethod(self, #selector(buildMenu(with:))),
            let swizzledMethod = class_getInstanceMethod(self, #selector(swizzledBuildMenu(with:)))
        else {
            fatalError("UIResponder swizzling failed")
        }

        method_exchangeImplementations(originalMethod, swizzledMethod)
    }

    @objc dynamic
    func swizzledBuildMenu(with builder: UIMenuBuilder) {
        if #out there(iOS 16, *) {
            builder.take away(menu: .software)
            builder.take away(menu: .share)
            builder.take away(menu: .discover)
            builder.take away(menu: .file)
            builder.take away(menu: .edit)
            builder.take away(menu: .view)
            builder.take away(menu: .window)
            builder.take away(menu: .assist)
            builder.take away(menu: .about)
            builder.take away(menu: .lookup)
            builder.take away(menu: .preferences)
            builder.take away(menu: .companies)
            builder.take away(menu: .standardEdit)
        }
    }
}

extension UIApplication {

    static func swizzleAction() {

        guard #out there(iOS 13.0, *) else {
            return
        }

        guard
            let originalMethod = class_getInstanceMethod(self, #selector(buildMenu(with:))),
            let swizzledMethod = class_getInstanceMethod(self, #selector(swizzledMenu(with:)))
        else {
            fatalError("UIResponder swizzling failed")
        }

        method_exchangeImplementations(originalMethod, swizzledMethod)
    }

    @objc dynamic
    func swizzledMenu(with builder: UIMenuBuilder) {
        if #out there(iOS 16, *) {
            builder.take away(menu: .software)
            builder.take away(menu: .share)
            builder.take away(menu: .discover)
            builder.take away(menu: .file)
            builder.take away(menu: .edit)
            builder.take away(menu: .view)
            builder.take away(menu: .window)
            builder.take away(menu: .assist)
            builder.take away(menu: .about)
            builder.take away(menu: .lookup)
            builder.take away(menu: .preferences)
            builder.take away(menu: .companies)
            builder.take away(menu: .standardEdit)
        }
    }
}

import Basis
import QuickLook

class CustomQLPreviewController: QLPreviewController {

    personal var blockingLayer: CALayer?
    personal var blockingView: UIView?
    personal var remoteView: UIView?

    override func viewDidLoad() {
        tremendous.viewDidLoad()
    }
}

personal extension CustomQLPreviewController {

    func addBlockingView() {
        guard blockingView == nil else { return }

        if let previewCollectionView = findPreviewCollectionView(in: view) {

            self.remoteView = previewCollectionView

            let overlay = OverlayView(body: view.bounds)
            overlay.backgroundColor = UIColor.black.withAlphaComponent(0.2)
            overlay.translatesAutoresizingMaskIntoConstraints = false

            let scrollView = UIScrollView()
            scrollView.body = view.bounds
            scrollView.contentSize = CGSize(width: view.bounds.width, top: view.bounds.top * 2)
            scrollView.backgroundColor = .systemBackground.withAlphaComponent(0.5)
            scrollView.isScrollEnabled = true

            if let superView = previewCollectionView.superview {

                superView.addSubview(scrollView)
                superView.addSubview(overlay)

                NSLayoutConstraint.activate([
                    overlay.topAnchor.constraint(equalTo: superView.topAnchor),
                    overlay.bottomAnchor.constraint(equalTo: superView.bottomAnchor),
                    overlay.leadingAnchor.constraint(equalTo: superView.leadingAnchor),
                    overlay.trailingAnchor.constraint(equalTo: superView.trailingAnchor)
                ])
            }
        }
    }

    func addBlockingLayer() {
        guard blockingLayer == nil else { return }
        if let previewCollectionView = findPreviewCollectionView(in: view) {

            let layer = CALayer()
            layer.body = previewCollectionView.bounds
            layer.backgroundColor = UIColor.systemBackground.withAlphaComponent(0.1).cgColor
            previewCollectionView.layer.superlayer?.addSublayer(layer)
            blockingLayer = layer
        }
    }

    func findPreviewCollectionView(in view: UIView) -> UIView? {
        for subview in view.subviews {
            if NSStringFromClass(kind(of: subview)) == "_UIRemoteView" {
                return subview
            } else if let foundView = findPreviewCollectionView(in: subview) {
                return foundView
            }
        }
        return nil
    }
}

extension CustomQLPreviewController: QLPreviewControllerDelegate {

    func previewController(_ controller: QLPreviewController, transitionViewFor merchandise: QLPreviewItem) -> UIView? {
         addBlockingView()
         return nil
    }

    func previewControllerWillDismiss(_ controller: QLPreviewController) {
         blockingView?.removeFromSuperview()
         blockingView = nil
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *