1

I am trying to support Apple's Markup of PDFs via UIDocumentInteractionController for files in my Documents folder on iPad. I want the documents edited in-place, so my app can load them again after the user is finished. I have set the Info.plist options for this, and the in-place editing does seem to work. Changes are saved to the same file.

When I bring up the UIDocumentInteractionController popover for the PDF, I am able to choose "Markup", which then shows the PDF ready for editing. I can edit it too. The problem is when I click "Done": I get a menu appear with the options "Save File To..." and "Delete PDF". No option just to close the editor or save.

The frustrating thing is, I can see via Finder that the file is actually edited in-place in the simulator, and is already saved when this menu appears. I just want the editor to disappear and not confuse the user. Ie I want "Done" to be "Done".

The Done menu, which includes not direct "Close" or "Save" options.

Perhaps related, and also annoying, is that while the markup editor is visible, there is an extra directory added to Documents called (A Document Being Saved By <<My App Name>>), and that folder is completely empty the whole time. Removing the folder during editing does not change anything.

Anyone have an idea if I am doing something wrong, or if there is a way to have the Done button simply dismiss?

Drew McCormack
  • 3,490
  • 1
  • 19
  • 23

1 Answers1

0

In case others have this issue, I believe it is a bug in UIDocumentInteractionController in how it sets up the QLPreviewController it uses internally. If I proxy the delegate of the QLPreviewController, and return .updateContents from previewController(_:editingModeFor:), it works as expected.

Here is my solution. The objective is simple enough, but actually capturing the private QLPreviewController was not easy, and I ended up using a polling timer. There may be a better way, but I couldn't find it.

import QuickLook

class ViewController: UIViewController, UIDocumentInteractionControllerDelegate {

    /// This wraps the original delegaet of the QLPreviewController,
    /// so we can return .updateContents from previewController(_:editingModeFor:)
    let delegateProxy = QLPreviewDelegateProxy()
            
    var documentInteractionController = UIDocumentInteractionController()
    
    /// A timer we use to update the QL controller
    /// Ideally, we would use callbacks or delegate methds, but couldn't
    /// find a satisfactory set to do the job. Instead we poll (like an animal)
    var quicklookControllerPollingTimer: Timer?
    
    /// Use this to track the preview controller created by UIDocumentInteractionController
    var quicklookController: QLPreviewController?
    
    /// File URL of the PDF we are editing
    var editURL: URL!
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        quicklookControllerPollingTimer?.invalidate()
    }
    
    @IBAction func showPopover(_ sender: Any?) {
        documentInteractionController.url = editURL
        documentInteractionController.delegate = self
        documentInteractionController.presentOptionsMenu(from: button.bounds, in: button, animated: true)
        
        quicklookControllerPollingTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [unowned self] timer in
            guard quicklookController == nil else {
                if quicklookController?.view.window == nil {
                    quicklookController = nil
                }
                return
            }
            if let ql = presentedViewController?.presentedViewController as? QLPreviewController, ql.view.window != nil {
                self.quicklookController = ql
                
                // Extra delay gives UI time to update
                DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
                    guard let ql = self.quicklookController else { return }
                    delegateProxy.originalDelegate = ql.delegate
                    ql.delegate = delegateProxy
                    ql.reloadData()
                }
            }
        }
    }
    
}


class QLPreviewDelegateProxy: NSObject, QLPreviewControllerDelegate {
    
    weak var originalDelegate: QLPreviewControllerDelegate?

    /// All this work is just to return .updateContents here. Doing this makes it all work properly.
    /// Must be a bug in UIDocumentInteractionController
    func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
        .updateContents
    }
    
}
Drew McCormack
  • 3,490
  • 1
  • 19
  • 23