UIDocumentBrowser 在 iPad 上无法运行,但在模拟器上运行良好 [iOS, Swift]
UIDocumentBrowser doesn’t work on iPads while works fine on a simulator [iOS, Swift]
我正在学习如何在 iOS 中构建 document-based
应用程序。
我按照 Apple 的官方示例 (https://developer.apple.com/documentation/uikit/view_controllers/building_a_document_browser-based_app#overview) 并尝试修改它以显示 PDF 查看器。
我修改了原始示例代码以创建以下代码。它可以与我的 Macbook Pro (2020) 上的任何模拟器完美配合。但是,当我使用 iPads(即 iPad Mini & iPad Pro:新一代)进行测试时,我无法打开 PDF 文件。
我确定以下代码不起作用:
let doc = PDFDocument(url: documentURL)
。
URL 似乎是适当获得的。 doc
在使用 iPads 测试时仍然是 nil
,而在使用模拟器测试时适当地获得。
如果您能告诉我我的代码有什么问题,我将不胜感激。
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
(我这里也是post这个,虽然这段代码好像不是问题)
/*
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)
嗨,我认为这可能是因为:
documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL])
访问沙盒外的项目。
你可以尝试替换:
presentDocument(at: url)
有
let data = try? Data(contentsOf: url)
print(data)
如果打印出 nil,那是因为 URL 加载失败。
然后替换为:
let didObtainSecScope = url.startAccessingSecurityScopedResource()
let data = try? Data(contentsOf: url)
print(data, didObtainSecScope)
url.stopAccessingSecurityScopedResource()
如果我的怀疑是正确的,它应该打印一个描述数据对象,然后是 'true'。您应该能够用您的 PDF 代码替换“Data(contentsOf: url)”。在使用 URL 时,您必须维护安全范围,然后在使用完 URL 中的文档后调用“url.stopAccessingSecurityScopedResource”。否则你会泄漏内核资源。
确保不要过早调用“stopAccessingSecurityScopedResource()”。
编辑:这里的苹果文档中明确提到了这一点:
https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
我正在学习如何在 iOS 中构建 document-based
应用程序。
我按照 Apple 的官方示例 (https://developer.apple.com/documentation/uikit/view_controllers/building_a_document_browser-based_app#overview) 并尝试修改它以显示 PDF 查看器。
我修改了原始示例代码以创建以下代码。它可以与我的 Macbook Pro (2020) 上的任何模拟器完美配合。但是,当我使用 iPads(即 iPad Mini & iPad Pro:新一代)进行测试时,我无法打开 PDF 文件。
我确定以下代码不起作用:
let doc = PDFDocument(url: documentURL)
。
URL 似乎是适当获得的。 doc
在使用 iPads 测试时仍然是 nil
,而在使用模拟器测试时适当地获得。
如果您能告诉我我的代码有什么问题,我将不胜感激。
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
(我这里也是post这个,虽然这段代码好像不是问题)
/*
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)
嗨,我认为这可能是因为:
documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL])
访问沙盒外的项目。
你可以尝试替换:
presentDocument(at: url)
有
let data = try? Data(contentsOf: url)
print(data)
如果打印出 nil,那是因为 URL 加载失败。
然后替换为:
let didObtainSecScope = url.startAccessingSecurityScopedResource()
let data = try? Data(contentsOf: url)
print(data, didObtainSecScope)
url.stopAccessingSecurityScopedResource()
如果我的怀疑是正确的,它应该打印一个描述数据对象,然后是 'true'。您应该能够用您的 PDF 代码替换“Data(contentsOf: url)”。在使用 URL 时,您必须维护安全范围,然后在使用完 URL 中的文档后调用“url.stopAccessingSecurityScopedResource”。否则你会泄漏内核资源。
确保不要过早调用“stopAccessingSecurityScopedResource()”。
编辑:这里的苹果文档中明确提到了这一点: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories