this is my first time trying to add an AR preview into an app (also my first time using the file system). I have been trying to implement a solution similar to that explained here https://developer.apple.com/forums/thread/126377 however one key difference is that my usdz model is not in my main bundle as it is generated and downloaded from an external source at run time. I was wondering if it is possible to display a file stored in the apps documents or cache directory and how it is done.
The file is downloaded and stored in the caches directory as follows:
class ModelFetcher: NSObject{
var modelUrl: URL?
func generateModel() {
guard let url = URL(string: "http://127.0.0.1:5000/model.usdz") else {return}
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
var request = URLRequest(url: url)
request.httpMethod = "POST"
let downloadTask = urlSession.downloadTask(with: request)
downloadTask.resume()
}
}
extension ModelFetcher: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("File Downloaded Location- ", location)
guard let url = downloadTask.originalRequest?.url else {
return
}
let docsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let destinationPath = docsPath.appendingPathComponent(url.lastPathComponent)
try? FileManager.default.removeItem(at: destinationPath)
do {
try FileManager.default.copyItem(at: location, to: destinationPath)
self.modelUrl = destinationPath
print("File moved to: \(modelUrl!.absoluteURL)")
} catch let error {
print("Copy Error: \(error.localizedDescription)")
}
}
}
I then try to display the model using ARQuicklookPreview as follows:
import SwiftUI
import QuickLook
import ARKit
struct ARQuickLookView: UIViewControllerRepresentable {
var allowScaling: Bool = true
func makeCoordinator() -> ARQuickLookView.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> QLPreviewController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
return controller
}
func updateUIViewController(_ controller: QLPreviewController,
context: Context) {
// nothing to do here
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
let parent: ARQuickLookView
let destinationPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("model.usdz")
private lazy var fileURL: URL = destinationPath
init(_ parent: ARQuickLookView) {
self.parent = parent
super.init()
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(
_ controller: QLPreviewController,
previewItemAt index: Int
) -> QLPreviewItem {
let fileURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("model.usdz")
print(fileURL)
let item = ARQuickLookPreviewItem(fileAt: fileURL)
print(item)
item.allowsContentScaling = parent.allowScaling
return item
}
}
}
struct ARQuickLookView_Previews: PreviewProvider {
static var previews: some View {
ARQuickLookView()
}
}
However, I receive the error "Unhandled item type 13: contentType is: (null) #PreviewItem" in the console and in the UI it reads unsupported file type, I have performed tests to make sure the file is in the location of the URL and I can even open it in preview on my mac so it's not like the file format or location is wrong.
Any help on displaying a model from a location other than the main bundle would be helpful, or perhaps a workaround to first move the file then display it.
Edit: After changing the preview controller function to
let fileURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("model.usdz")
return fileUrl as QLPreviewItem
Now in the UI it just says "model universal scene description Package"
And in the console I still get an error however now it reads: Unhandled item type 13: contentType is: com.pixar.universal-scene-description-mobile #PreviewItem
Which doesn't make sense as that it one of the only types it supports
Thanks,
Louis
Related
I'm very new in iOS development. So I'm trying to download pdf or image from firebase storage with URL using Alamofire and then I want to display with quicklook. I tried this example but no luck. Present preview of downloaded file directly in app
import UIKit
import Alamofire
import QuickLook
class DocumentViewer: UIViewController{
var previewItem = URL?.self
var refrenceDocURL: URL? // here i get the url from another view controller but never use it because I don't know where should I use it.
func downloadFile(fileUrl: URL) {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in //ERROR: 'DownloadFileDestination' is not a member type of class 'Alamofire.DownloadRequest'
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent(documentFilename)
return (fileURL, [.removePreviousFile])
}
Alamofire.download(fileUrl, to: destination) //ERROR: Module 'Alamofire' has no member named 'download'
.response(completionHandler: { (downloadResponse) in
let previewController = QLPreviewController()
previewController.dataSource = self
self.previewItem = downloadResponse.destinationURL
self.present(previewController, animated: true, completion: nil)
})
}
}
extension DocumentViewer: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return self.previewItem as! QLPreviewItem
}
}
Looks like you're using old definitions.
DownloadRequest.DownloadFileDestination is DownloadRequest.Destination.
Alamofire.download is AF.download.
The app needs to create a URL to pass into a UIDocumentInteractionController that will present the user with options to share the file. It's a PDF that is downloaded over the network and confirmed as being in place.
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let filePath = documentsPath.appendingPathComponent("documents/\(self.filename!)").path
let fileURL = URL(fileURLWithPath: filePath)
This is where the PDF is stored after being downloaded.
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)
This opens up the menu with the sharing options but when trying to select an app to open the PDF, the following error occurs:
Could not instantiate class NSURL. Error: Error Domain=NSCocoaErrorDomain Code=4864 "The URL archive of type “public.url” contains invalid data." UserInfo={NSDebugDescription=The URL archive of type “public.url” contains invalid data.}
After having confirmed the document is where it should be, when printing the fileURL, it's the following:
file:///var/mobile/Containers/Data/Application/3296B736-4DFB-4F62-9B05-C800D574982B/Documents/documents/77351848-68816600-1626168959.pdf
I have searched high and low for an answer to this and I believe it's more linked to the URL than the UIDocumentInteractionController itself.
Any advice would be enormously appreciated.
Swift 5 Implementation(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)")
}
}
}
My application have file download option which downloads the file using alamofire download method. When the download completes i need to present the preview of the file without saving it to internal/cloud storage. How can i achieve this whatsapp like function that shows the preview after downloading the file.
func downloadFile(fileUrl: URL) {
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(fileUrl, to: destination)
.response(completionHandler: { (downloadResponse) in
self.dic.url = downloadResponse.destinationURL
self.dic.uti = downloadResponse.destinationURL!.uti
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
self.dic.presentOpenInMenu(from: rect, in: self.view, animated: true)
})
}
To present a preview of a file use Apple’s QuickLook framework that lets you embed previewing for a huge range of file types, including iWork documents, Microsoft Office documents, PDFs, images, and more, all without writing much code.
First, import the QuickLook framework, then make your view controller conform to the QLPreviewControllerDataSource protocol.
Reference:
https://www.hackingwithswift.com/example-code/libraries/how-to-preview-files-using-quick-look-and-qlpreviewcontroller
https://github.com/gargsStack/QLPreviewDemo
https://www.appcoda.com/quick-look-framework/
Code:
class ViewController: UIViewController {
var previewItem = URL!
func downloadFile(fileUrl: URL) {
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(fileUrl, to: destination)
.response(completionHandler: { (downloadResponse) in
let previewController = QLPreviewController()
previewController.dataSource = self
self.previewItem = downloadResponse.destinationURL
self.present(previewController, animated: true, completion: nil)
})
}
}
extension ViewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return self.previewItem as QLPreviewItem
}
}
Here is one solution using Alamofire. Someone may help.
Steps:
Alamofire has excellent staff to direct download and also save/write
your file into disc.
Return a path where downloaded file saved.
Using UIDocumentInteractionController pass the file path
Then present this view
import Alamofire
extension UIViewController : UIDocumentInteractionControllerDelegate{
func downloadFileForPreview(fileName: String, fileExtension: String, filePath: String ) {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileWithExtension = "file.\(fileExtension)"
let fileURL = documentsURL.appendingPathComponent(fileWithExtension)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
//UtilitySwift.showUniversalLoadingView(true)
Alamofire.download(filePath, to: destination).response { response in
debugPrint(response)
//UtilitySwift.showUniversalLoadingView(false)
if response.error == nil, let storeFilePath = response.destinationURL?.path {
//let image = UIImage(contentsOfFile: imagePath)
self.previewDocument(withFilePath: response.destinationURL)
print(storeFilePath)
}
else{
UtilitySwift.showErrorMessage(message: response.error?.localizedDescription ?? "Error occured when downloading" )
print(response.error?.localizedDescription ?? "")
}
}
}
// Converted to Swift 5.1 by Swiftify v5.1.29672 - https://objectivec2swift.com/
func previewDocument(withFilePath filePath: URL?) {
var documentInteractionController: UIDocumentInteractionController?
if filePath != nil {
// Initialize Document Interaction Controller
if let filePath = filePath {
documentInteractionController = UIDocumentInteractionController(url: filePath)
}
// Configure Document Interaction Controller
documentInteractionController?.delegate = self as UIDocumentInteractionControllerDelegate
//if not possible to open
if !documentInteractionController!.presentPreview(animated: true) {
documentInteractionController?.presentOptionsMenu(from: CGRect.zero, in: self.view, animated: true)
}
} else {
// error
print("file preview error")
}
}
//UIDocumentInteractionControllerDelegate
public func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
self
}
}
Call from any UIViewController
self.downloadFileForPreview(fileName: "file", fileExtension: fileExt ?? "", filePath: REPLACE_WITH_DOWNLOAD_URL)
so this might be a trivial question, but I can't get it to work.
I want to save a pdf file to CoreData after I dropped it onto a view (using IOS' Drag&Drop feature)
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
session.loadObjects(ofClass: ComicBookPDFDocument.self) { (pdfItems) in
let items = pdfItems as! [ComicBookPDFDocument]
// "Cannot assign value of type 'ComicBookPDFDocument' to type 'NSData?'"
self.file.data = items[0]
}
}
ComicBookPDFDocument just subclasses PDFDocument to make it conforming to NSItemProviderReading:
final class ComicBookPDFDocument: PDFDocument, NSItemProviderReading {
public static var readableTypeIdentifiersForItemProvider: [String] {
return [kUTTypePDF as String]
}
public static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> ComicBookPDFDocument {
return ComicBookPDFDocument(data: data)!
}
}
However, I get this compiler error from XCode:
Cannot assign value of type 'ComicBookPDFDocument' to type 'NSData?'
How can I save the pdf data from a PDFDocument? I couldn't find anything on the internet or the documentation.
Thanks for any help
Okay, I don't know how I missed that, but here it is:
items[0].dataRepresentation()
You do one thing,
Try to save PDF into the Document Directory and save its path in the Core-Data.
Here is the code to save to Document directory and fetch from document direcory
class PDCache: NSObject {
static let sharedInstance = PDCache()
func saveData(obj: Data, fileName: String){
let filename = getDocumentsDirectory().appendingPathComponent("\(fileName).pdf")
do{
try obj.write(to: filename, options: .atomic)
} catch{
print(error.localizedDescription)
}
}
func getData(fileName: String) -> URL?{
let fileManager = FileManager.default
let filename = getDocumentsDirectory().appendingPathComponent("\(fileName).pdf")
if fileManager.fileExists(atPath: filename.path){
return URL(fileURLWithPath: filename.path)
}
return nil
}
private func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
}
To save the data use this
let data = Data()
self.saveData(obj: data, fileName: "myPdfFile")
and to get the file url use this
let pdfUrl = self.getData(fileName: "myPdfFile")
Try this and let me know if it works for you.
class ViewController: UIViewController {
let quickLookController = QLPreviewController()
override func viewDidLoad() {
super.viewDidLoad()
quickLookController.dataSource = self
quickLookController.delegate = self
}
#IBAction func buttonAction(_ sender: Any) {
present(quickLookController, animated: true, completion: nil)
quickLookController.reloadData()
quickLookController.refreshCurrentPreviewItem()
}
}
extension ViewController: QLPreviewControllerDataSource,QLPreviewControllerDelegate {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let path = Bundle.main.path(forResource: "AppCoda-Ppt.ppt", ofType: nil)
let url = NSURL(fileURLWithPath: path!)
return url
}
}
This is a bug in iOS 11.2. Please file it at bugreport.apple.com if you want to be kept up to date on its status. A workaround is not to store your ppt file in your app bundle. Use a different location somewhere in your container, such as your Application Support directory.
Answer from #Thomas is correct (THANK YOU!) For Swift 3 you can do something like this in the previewItemAt:index method to cache the file on demand:
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
guard let CacheDirURL = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
print("Can't get cache dir URL")
return NSURL(fileURLWithPath: "FILE_NOT_FOUND")
}
let fileUrl = CacheDirURL.appendingPathComponent("cachedFileName.pdf")
if !FileManager.default.fileExists(atPath: fileUrl.path) {
if let sourceUrl = Bundle.main.url(forResource: "Pioneer_UAS_Policy_Aug_2016", withExtension: "pdf") {
print("Copying file from bundle \(sourceUrl.path)")
try? FileManager.default.copyItem(at: sourceUrl, to: fileUrl)
}
}
return NSURL(fileURLWithPath: fileUrl.path)
}
You have to copy the file to the i.e. temporary directory and read the file from there.