简体   繁体   中英

PDFKit doesn’t work on iPads while works fine on a simulator [iOS, Swift]

I am learning about how to build a document-based app in iOS.

I followed Apple's official example ( https://developer.apple.com/documentation/uikit/view_controllers/building_a_document_browser-based_app#overview ) and tried to revise it to display a PDF viewer.

I modified the original sample code to create the following code. It works totally fine with any simulators on my Macbook Pro (2020). However, when I tested with iPads (ie, iPad Mini & iPad Pro: new generations), I cannot open PDF files.

I identified that the following code is not working: let doc = PDFDocument(url: documentURL) . The URL seems to be appropriately obtained. doc remains to be nil when testing with iPads, while appropriately obtained when testing with simulators.

I would really appreciate it if you could let me know what is wrong with my code.

DocumentBrowserController.swift

import UIKit
import os.log
import PDFKit

/// - Tag: DocumentBrowserViewController
class DocumentBrowserViewController: UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate {
    
    /// - Tag: viewDidLoad
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
        
        allowsDocumentCreation = true
        allowsPickingMultipleItems = false
    }
    
     //MARK: - UIDocumentBrowserViewControllerDelegate
    
    // UIDocumentBrowserViewController is telling us to open a selected a document.
    func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) {
        if let url = documentURLs.first {
            presentDocument(at: url)
        }
    }
    
    // MARK: - Document Presentation
   var transitionController: UIDocumentBrowserTransitionController?
    
    func presentDocument(at documentURL: URL) {
        let storyBoard = UIStoryboard(name: "Main", bundle: nil)

        // Load the document's view controller from the storyboard.
        let instantiatedNavController = storyBoard.instantiateViewController(withIdentifier: "DocNavController")
        guard let docNavController = instantiatedNavController as? UINavigationController else { fatalError() }
        guard let documentViewController = docNavController.topViewController as? TextDocumentViewController else { fatalError() }
        
//        // Load the document view.
//        documentViewController.loadViewIfNeeded()
//
        // In order to get a proper animation when opening and closing documents, the DocumentViewController needs a custom view controller
        // transition. The `UIDocumentBrowserViewController` provides a `transitionController`, which takes care of the zoom animation. Therefore, the
        // `UIDocumentBrowserViewController` is registered as the `transitioningDelegate` of the `DocumentViewController`. Next, obtain the
        // transitionController, and store it for later (see `animationController(forPresented:presenting:source:)` and
        // `animationController(forDismissed:)`).
        docNavController.transitioningDelegate = self
        
        // Get the transition controller.
        transitionController = transitionController(forDocumentAt: documentURL)
        
        let doc = PDFDocument(url: documentURL)

        transitionController!.targetView = documentViewController.pdfView
  
        
        // Present this document (and it's navigation controller) as full screen.
        docNavController.modalPresentationStyle = .fullScreen

        // Set and open the document.
        documentViewController.document = doc
        os_log("==> Document Opened", log: .default, type: .debug)
        self.present(docNavController, animated: true, completion: nil)
    }
    
}

extension DocumentBrowserViewController: UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController,
                             presenting: UIViewController,
                             source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // Since the `UIDocumentBrowserViewController` has been set up to be the transitioning delegate of `DocumentViewController` instances (see
        // implementation of `presentDocument(at:)`), it is being asked for a transition controller.
        // Therefore, return the transition controller, that previously was obtained from the `UIDocumentBrowserViewController` when a
        // `DocumentViewController` instance was presented.
        return transitionController
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // The same zoom transition is needed when closing documents and returning to the `UIDocumentBrowserViewController`, which is why the the
        // existing transition controller is returned here as well.
        return transitionController
    }
    
}

TextDocumentViewController.swift

(I just post this here too, although this code does not seem to be the problem)

/*
 See LICENSE folder for this sample’s licensing information.
 
 Abstract:
 A view controller for displaying and editing documents.
 */

import UIKit
import os.log
import PDFKit

var currentPage = 0

/// - Tag: textDocumentViewController
class TextDocumentViewController: UIViewController, PDFDocumentDelegate, PDFViewDelegate {
    
    @IBOutlet weak var pdfView: PDFView!
    @IBOutlet weak var doneButton: UIBarButtonItem!
    
    private var keyboardAppearObserver: Any?
    private var keyboardDisappearObserver: Any?
    
    var document: PDFDocument! {
        didSet {
            document.delegate = self
        }
    }
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        //        setupNotifications()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        //        setupNotifications()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        pdfView.delegate = self
        doneButton.isEnabled = false
        
        if #available(iOS 13.0, *) {
            /** When turned on, this changes the rendering scale of the text to match the standard text scaling
             and preserves the original font point sizes when the contents of the text view are copied to the pasteboard.
             Apps that show a lot of text content, such as a text viewer or editor, should turn this on and use the standard text scaling.
             
             For more information, refer to the WWDC 2019 video on session 227 "Font Management and Text Scaling"
             https://developer.apple.com/videos/play/wwdc2019/227/
             (from around 30 minutes in, and to the end)
             */
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        pdfView.document = document!
        pdfView.autoScales = true
        pdfView.displayMode  = .singlePage
        pdfView.displayDirection = .horizontal
        pdfView.displaysPageBreaks = true
        pdfView.pageBreakMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
        pdfView.usePageViewController(true)                   
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
    }
    
    
    
}

* I simplified the code trying to illustrate the problem clearly (2020/10/27)

Hi I think this might be because:

documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) 

Access' items out side of the sandbox.

Can you try replacing:

 presentDocument(at: url)

With

let data = try? Data(contentsOf: url)
print(data)

If this prints nil, it's because the URL is failing to load.

Then replace it with:

   let didObtainSecScope = url.startAccessingSecurityScopedResource()
   let data = try? Data(contentsOf: url)
   print(data, didObtainSecScope)
   url.stopAccessingSecurityScopedResource()

If my suspicion is correct, it should print a description data object, followed by 'true'. You should be able to replace "Data(contentsOf: url)" with your PDF code. You will have to maintain the security scope while you are using the URL, and then call "url.stopAccessingSecurityScopedResource" once you are finished using the document at the URL. Otherwise you will leak kernel resources.

Ensure you don't call "stopAccessingSecurityScopedResource()" too early.

Edit: This is explicitly mentioned in apples documentation here: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM