Search code examples
iosswiftipadosios-pdfkit

Swift PDFKit: Inconsistent behaviour with PDFView.currentDestination and PDFView.go(to destination: PDFDestination)


Note: The question remains unsolved for now; the marked answer provides a good workaround - it works while the application is still open. Answers are still welcomed!

Background

I'm currently developing an app that consists of a full-screen PDFView, and I want the program to remember the position in the document before the view is dismissed so the user can pick up where they've left.

Implementation

A simplified version of the app can be understood as a PDF Viewer using PDFKit.PDFView. The storyboard consists of an UIView that's connected to a PDFView class, DocumentView (which conforms to UIViewController). The view is initialised through the following process:

In viewDidLoad:

let PDF: PDFDocument = GetPDFFromServer()
DocumentView.document = PDF!
DocumentView.autoScales = true
... (other settings)

// Set PDF Destination
let Destination: PDFDestination = GetStoredDestination()
// Code with issues
DocumentView.go(to: Destination)

In viewWillDisappear:

StoreDestination(DocumentView.currentDestination)

The Issue & Tests

I realised that the code does not work as expected; the view does not return to its previous location.

Through debugging, I realised that this might be due to the inconsistent behaviour of DocumentView.go(to destination: PDFDestination) and DocumentView.currentDestination.

To ensure the bug is not introduced by errors while storing the location, the following code is used to verify the issue, with a multi-page document:

In viewDidLoad

Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
    DispatchQueue.main.async {
    
self.DocumentView.go(to:self.DocumentView.currentDestination!)
    }

})

Expected & Observed behaviour

Expected: The location of the document should not change - the code is going to its current destination every 1 second which should have no effects. as "currentDestination" should be the "current destination of the document, per docs")

Observed: Upon execution, the page would spontaneously scroll down by a fixed offset.

The same outcome was observed on an iPadOS 14.5 simulator and an iPadOS 15 iPad Air (Gen 4).

What might have gone wrong?

It'd be great if somebody can help.

Cheers, Lincoln

This question was originally published on the Apple Developer Forum over a week ago; No responses were heard for over a week, so I thought I might try my luck here on StackOverflow <3


Solution

  • I tried PDFView.go() for this scenario and I managed to get it work for some cases but found that it fails in some other scenarios such as with zoomed documents, changed orientations.

    So going back to what you are trying to achieve,

    I'm currently developing an app that consists of a full-screen PDFView, and I want the program to remember the position in the document before the view is dismissed so the user can pick up where they've left.

    this can be done from a different approach. With this approach, you need to always keep a reference to the PDFView you created. If the previous pdf needs to be loaded again, then you pass the PDFView instance you have to the viewController as it is. Otherwise you load the new pdf to the PDFView instance and pass it to the viewController.

    DocumentViewController gets the PDFView when it gets initialized.

    import UIKit
    import PDFKit
    
    protocol DocumentViewControllerDelegate: AnyObject {
        func needsContinuePDF(continuePDF: Bool)
    }
    
    class DocumentViewController: UIViewController {
        var pdfView: PDFView!
        weak var delegate: DocumentViewControllerDelegate!
        
        init(pdfView: PDFView, delegate: DocumentViewControllerDelegate){
            self.pdfView = pdfView
            self.delegate = delegate
            super.init(nibName: nil, bundle: nil)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func loadView() {
            super.loadView()
            self.view.backgroundColor = .white
            view.addSubview(pdfView)
            NSLayoutConstraint.activate([
                pdfView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
                pdfView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
                pdfView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
                pdfView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
            ])
        }
        
        override func viewWillDisappear(_ animated: Bool) {
            delegate.needsContinuePDF(continuePDF: true)
        }
    }
    

    You can initialize DocumentViewController like below. MainViewController has the responsibility for initializing PDFView.

    import UIKit
    import PDFKit
    
    class MainViewController: UIViewController {
        var pdfView: PDFView = PDFView()
        var continuePreviousPDF = false
        
        let button = UIButton(frame: .zero)
        
        override func loadView() {
            super.loadView()
            
            button.setTitle("View PDF", for: .normal)
            button.setTitleColor(.white, for: .normal)
            button.backgroundColor = .systemBlue
            button.addTarget(self, action: #selector(openDocumentView(_:)), for: .touchUpInside)
            self.view.backgroundColor = .systemGray5
            self.view.addSubview(button)
            button.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                button.widthAnchor.constraint(equalToConstant: 100),
                button.heightAnchor.constraint(equalToConstant: 50),
                button.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor),
                button.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor),
            ])
        }
        
        @objc func openDocumentView(_ sender: UIButton) {
            //open a nee PDF if not continue previous one
            if !self.continuePreviousPDF {
                pdfView.autoScales = true
                pdfView.displayMode = .singlePageContinuous
                pdfView.translatesAutoresizingMaskIntoConstraints = false
                
                guard let path = Bundle.main.url(forResource: "sample copy", withExtension: "pdf") else { return }
    
                if let document = PDFDocument(url: path) {
                    pdfView.document = document
                }
            }
            
            let documentViewController = DocumentViewController(pdfView: pdfView, delegate: self)
            self.present(documentViewController, animated: true, completion: nil)
        }
    }
    
    extension MainViewController: DocumentViewControllerDelegate {
        func needsContinuePDF(continuePDF: Bool) {
            self.continuePreviousPDF = continuePDF
        }
    }
    

    enter image description here