应用程序需要创建一个
URL
来传递给 UIDocumentInteractionController
,这将为用户提供共享文件的选项。这是通过网络下载并确认到位的 PDF。
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let filePath = documentsPath.appendingPathComponent("documents/\(self.filename!)").path
let fileURL = URL(fileURLWithPath: filePath)
这是下载后保存 PDF 的位置。
let documentController: UIDocumentInteractionController = UIDocumentInteractionController.init(url: fileURL)
documentController.uti = "com.adobe.pdf"
documentController.presentOpenInMenu(from: CGRect(x: 0, y: 0, width: self.container.frame.width, height: self.container.frame.height), in: self.container, animated: true)
这将打开带有共享选项的菜单,但当尝试选择应用程序来打开 PDF 时,会出现以下错误:
无法实例化 NSURL 类。错误:错误域 = NSCocoaErrorDomain 代码 = 4864 ““public.url”类型的 URL 存档包含无效数据。” UserInfo={NSDebugDescription=“public.url”类型的 URL 存档包含无效数据。}
确认文档位置正确后,打印 fileURL 时,如下:
文件:///var/mobile/Containers/Data/Application/3296B736-4DFB-4F62-9B05-C800D574982B/Documents/documents/77351848-68816600-1626168959.pdf
我到处寻找这个问题的答案,我相信它与
URL
的联系比 UIDocumentInteractionController
本身更相关。
任何建议将不胜感激。
Swift 5 实现(Xcode 12.5):
class VC1: UIViewController {
var pdfURL: URL?
var documentInteractionController:UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func downloadAction(_ sender: Any) {
self.downloadPDF()
}
func downloadPDF() {
guard let url = URL(string: "https://file-examples-com.github.io/uploads/2017/10/file-sample_150kB.pdf") else { return }
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
let downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
}
func showPDF(url: URL) {
documentInteractionController = UIDocumentInteractionController(url: url)
documentInteractionController.delegate = self
DispatchQueue.main.async { [self] in
documentInteractionController.presentPreview(animated: true)
}
}
}
extension VC1: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
func documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) {
print("Dismissed!!!")
documentInteractionController = nil
}
}
extension VC1: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let url = downloadTask.originalRequest?.url else { return }
let documentsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
try? FileManager.default.removeItem(at: destinationURL)
do {
try FileManager.default.copyItem(at: location, to: destinationURL)
self.pdfURL = destinationURL
showPDF(url: destinationURL)
} catch let error {
print("Error: \(error.localizedDescription)")
}
}
}
使用Combine 实现 Swift 5
private func downloadPDF(_ sender: UIButton) {
guard let pdfURL = pdf else {
sender.configuration?.showsActivityIndicator = false
return
}
URLSession.shared.downloadTask(with: pdfURL)
.subscribe(on: DispatchQueue.global(qos: .background)) // Run on background thread
.tryMap { (location, response) -> URL in
// Destination URL in cache directory
let documentsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent(pdfURL.lastPathComponent)
// Remove existing file if it exists
try? FileManager.default.removeItem(at: destinationURL)
// Copy downloaded file to destination URL
try FileManager.default.copyItem(at: location, to: destinationURL)
return destinationURL
}
.receive(on: DispatchQueue.main) // Ensure UI updates happen on the main thread
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Download failed with error: \(error.localizedDescription)")
case .finished: break
}
sender.configuration?.showsActivityIndicator = false
}, receiveValue: { [weak self] destinationURL in
self?.showPDF(destinationURL)
})
.store(in: &cancellables)
}
private func showPDF(_ url: URL) {
documentInteractionController = UIDocumentInteractionController(url: url)
documentInteractionController?.delegate = self
documentInteractionController?.presentPreview(animated: true)
}
URLSession+组合扩展
extension URLSession {
/// Custom Combine publisher for URLSession download tasks
func downloadTask(with url: URL) -> URLSession.DownloadTaskPublisher {
return DownloadTaskPublisher(url: url, session: self)
}
struct DownloadTaskPublisher: Publisher {
typealias Output = (URL, URLResponse)
typealias Failure = URLError
private let url: URL
private let session: URLSession
init(url: URL, session: URLSession) {
self.url = url
self.session = session
}
func receive<S>(subscriber: S) where S : Subscriber, URLError == S.Failure, (URL, URLResponse) == S.Input {
let subscription = DownloadTaskSubscription(subscriber: subscriber, url: url, session: session)
subscriber.receive(subscription: subscription)
}
private final class DownloadTaskSubscription<S: Subscriber>: Subscription where S.Input == (URL, URLResponse), S.Failure == URLError {
private var subscriber: S?
private let url: URL
private let session: URLSession
private var task: URLSessionDownloadTask?
init(subscriber: S, url: URL, session: URLSession) {
self.subscriber = subscriber
self.url = url
self.session = session
startTask()
}
func request(_ demand: Subscribers.Demand) {}
func cancel() {
task?.cancel()
subscriber = nil
}
private func startTask() {
task = session.downloadTask(with: url) { [weak self] (location, response, error) in
guard let self = self else { return }
if let error = error as? URLError {
self.subscriber?.receive(completion: .failure(error))
} else if let location = location, let response = response {
_ = self.subscriber?.receive((location, response))
self.subscriber?.receive(completion: .finished)
}
}
task?.resume()
}
}
}
}