UIDocumentBrowser не работает на iPad, в то время как отлично работает на симуляторе [iOS, Swift]

#ios #swift #uikit #pdfkit #document-based

Вопрос:

Я изучаю, как создать document-based приложение в iOS.

Я последовал официальному примеру Apple(https://developer.apple.com/documentation/uikit/view_controllers/building_a_document_browser-based_app#overview) и попытался изменить его, чтобы отобразить средство просмотра PDF.

Я изменил исходный пример кода, чтобы создать следующий код. Он отлично работает с любыми симуляторами на моем Macbook Pro (2020). Однако, когда я тестировал с iPad (например, iPad Mini и iPad Pro: новые поколения), я не смог открыть PDF-файлы.

Я определил, что следующий код не работает: let doc = PDFDocument(url: documentURL) . URL — адрес, по-видимому, получен надлежащим образом. doc остается nil неизменным при тестировании с iPad, в то время как надлежащим образом получается при тестировании с помощью симуляторов.

Я был бы очень признателен, если бы вы могли сообщить мне, что не так с моим кодом.

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

(Я просто публикую это здесь тоже, хотя этот код, похоже, не является проблемой)

 /*
 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)
        
    }
    
    
    
}
 

* Я упростил код, пытаясь четко проиллюстрировать проблему (2020/10/27)

Комментарии:

1. Ты должен окончить школу лесоруба… В любом случае, так каковы же URL-адреса? Что говорит ваш «Импортированный документ из %@ в%@»?

2. Спасибо, что спросили, @matt! Это первый раз, когда я использую log его ; я использую его в качестве примера используемого кода. Я попытался просмотреть содержимое Imported A Document from %@ to %@ , но почему-то не могу остановиться ни на одной точке останова. Вместо этого я смог увидеть url at func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) { if let url = documentURLs.first . Это т гласит следующее: _url NSURL "file:///private/var/mobile/Library/Mobile Documents/3L68KQB4HG~com~readdle~CommonDocuments/Documents/filename.pdf" 0x0000000282948180

3. Как вы получили URL-адрес? Если вы создадите URL-адрес вручную, имейте в виду, что между симулятором и реальным устройством существуют несколько разные пути.

4. Спасибо, что спросили! Я получил url с помощью своего iPad Pro, установив точку останова, в которой я выбираю файл с помощью func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) { if let url = documentURLs.first { presentDocument(at: url) } } . При использовании моего MacBook Pro url он читается как url Foundation.URL "file:///Users/akifumiyanagisawa/Library/Developer/CoreSimulator/Devices/4A3E89D5-C25C-4D14-A6E4-02D3A4E60DB4/data/Library/Mobile Documents/3L68KQB4HG~com~readdle~CommonDocuments/Documents/filename.pdf" .

5. Извините, я не совсем ясно выразился, когда спрашивал. Я имею в виду, как URL-адрес получил свою ценность. Был ли путь собран вручную и передан в URL.init или он был получен другими способами?

Ответ №1:

Привет, я думаю, это может быть потому, что:

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

Доступ к элементам вне песочницы.

Можете ли вы попробовать заменить:

  presentDocument(at: url)
 

С

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

Если при этом выводится ноль, это связано с тем, что URL-адрес не загружается.

Затем замените его на:

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

Если мои подозрения верны, он должен напечатать объект данных описания, за которым следует «true». Вы должны иметь возможность заменить «Данные(содержимое: URL)» своим кодом PDF. Вам нужно будет поддерживать область безопасности во время использования URL-адреса, а затем вызвать «url.stopAccessingSecurityScopedResource», как только вы закончите использовать документ по URL-адресу. В противном случае произойдет утечка ресурсов ядра.

Убедитесь, что вы не вызываете «stopAccessingSecurityScopedResource()» слишком рано.

Изменить: Это явно упоминается в документации apple здесь: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories

Комментарии:

1. Большое вам спасибо, @Rufus Mall!! Вы были абсолютно правы в этом, и я смог решить проблему, следуя вашему предложению!!! Я ввел guard url.startAccessingSecurityScopedResource() else { print ("file handling error") return } presentDocument(at: url) свой код. 🙂 Я подумаю, чтобы поставить stopAccessingSecurityScopedResource() где-нибудь красиво. Я действительно ценю вашу помощь и надеюсь, что у вас сегодня будет замечательный день!

2. Нет проблем, просто убедитесь, что вы не забыли позвонить.stopAccessingSecurityScopedResource()