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