0

Self-teaching novice here.

My end goal:

iOS/Mac app that loads a directory of PDFs, searches each for an array of strings, and lists which PDFs contain which strings where.

Problem in prototyping for only one PDF:

I receive a perplexing nil from loading a chosen PDF, running .beginFindStrings(["and", "the"], withOptions: .caseInsensitive) and waiting for the Notification .PDFDocumentDidEndFind to check [PDFSelection] .

That shouldn't be. Memory shows the PDF is loaded. Am I doing something wrong with threads? I think I've followed the async advice here: PDFKit background search

Code

import UIKit
import MobileCoreServices
import PDFKit

class ViewController: UIViewController, UIDocumentPickerDelegate {

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

    var matchesFound: PDFSelection?

    @IBOutlet weak var resultsLabel: UILabel!

    @IBAction func importPDF(_ sender: Any) {
        let picker = UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .import)
        picker.delegate = self
        picker.allowsMultipleSelection = false
        self.present(picker, animated: true)
    }

    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard urls.count == 1 else {return}
        let data = try! Data(contentsOf: urls[0])
        let subjectPDF = PDFDocument.init(data: data)
        guard subjectPDF!.isLocked == false else {return}

        subjectPDF!.beginFindStrings(["the", "and"], withOptions: .caseInsensitive)

        NotificationCenter.default.addObserver(self, selector: #selector(onDidFindMatch(_:)), name: Notification.Name.PDFDocumentDidEndFind, object: nil)
    }

    @objc func onDidFindMatch(_ notification: Notification) {
        resultsLabel.text = "\(String(describing: matchesFound?.string))"
    }

    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            dismiss(animated: true, completion: nil)
    }

}

Code with Markup

import UIKit
import MobileCoreServices
import PDFKit

class ViewController: UIViewController, UIDocumentPickerDelegate {

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

//Array of PDFSelection search results
    var matchesFound: PDFSelection?

//Temporary display for search result strings
    @IBOutlet weak var resultsLabel: UILabel!

//Choose a PDF to import, temporarily limited to one
    @IBAction func importPDF(_ sender: Any) {
        let picker = UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .import)
        picker.delegate = self
        picker.allowsMultipleSelection = false
        self.present(picker, animated: true)
    }

//Load the picked PDF as subjectPDF, if unlocked
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        guard urls.count == 1 else {return}
        let data = try! Data(contentsOf: urls[0])
        let subjectPDF = PDFDocument.init(data: data)
        guard subjectPDF!.isLocked == false else {return}

//Find temporary array of strings
        subjectPDF!.beginFindStrings(["the", "and"], withOptions: .caseInsensitive)

//Trigger results readout upon search competion
        NotificationCenter.default.addObserver(self, selector: #selector(onDidFindMatch(_:)), name: Notification.Name.PDFDocumentDidEndFind, object: nil)
    }

//Readout found strings to temporary label
    @objc func onDidFindMatch(_ notification: Notification) {
        resultsLabel.text = "\(String(describing: matchesFound?.string))"
    }

    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            dismiss(animated: true, completion: nil)
    }

}


Beginner
  • 455
  • 7
  • 14

1 Answers1

0

The question was asked 7 months ago though, I hope you already found the solution. Anyway the solution for your problem:

1- Move the line below to the viewDidLoad(), because you are adding an observer after triggering the beginFindString() method.

NotificationCenter.default.addObserver(self, selector: #selector(onDidFindMatch(_:)), name: Notification.Name.PDFDocumentDidEndFind, object: nil)

2- You are never assigning any value to matchesFound variable, so it's always nil.

3- To get the matches from beginFindString method, you need to add an observer for PDFDocumentDidFindMatch and get the data from userInfo instead of PDFDocumentDidEndFind. PDFDocumentDidEndFind observer will be called when searching has been finished, you can use this observer for removing you loading view for instance.

Here is a sample code of the correct implementation:

var matchesFound = [PDFSelection]()

// MARK: Life cycle

override func viewDidLoad() {
    super.viewDidLoad()
    
    NotificationCenter.default.addObserver(self, selector: #selector(didFindMatch(_:)), name: NSNotification.Name.PDFDocumentDidFindMatch, object: nil)
}

deinit {
    NotificationCenter.default.removeObserver(self)
}

// This method will get called every-time a match has been found.
@objc private func didFindMatch(_ sender: Notification) {
    guard let selection = sender.userInfo?["PDFDocumentFoundSelection"] as? PDFSelection else { return }

    self.matchesFound.append(selection)
}
Shahriyar
  • 520
  • 7
  • 18
  • Thanks for rounding back to an old question. I ended up just converting the thing to text and that worked much easier for my purpose of querying it for strings in proximity to each other. – Beginner Jun 23 '20 at 07:07