Display AR QuickLook preview of usdz model not in main bundle

Hi all, 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)")
    }
  }
}

and then the quick look preview looks like this:

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 get an error reading "Unhandled item type 13: contentType is: (null) #PreviewItem"

I know the file is actually located at this location as I have opened it in Finder. Any help would be much appreciated.

Thanks in advance, Louis

Accepted Reply

Just in case anyone else stumbles upon the same issue I was able to fix it by replacing the last 5 lines in the previewController func with:

return fileUrl as QLPreviewItem

Replies

I know the file is actually located at this location as I have opened it in Finder.

The first thing I’d do in this situation is separate any potential file system problems from the AR QuickLook side of things. If you replace ARQuickLookPreviewItem(fileAt:) with a call to Data(contentsOf:), does that work? If so, you know this isn’t a file system issue and you have to focus on AR QuickLook. OTOH, if it fails then you know that you have a file system problem (and I can help you investigate that further).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • So when I make a call to Data(contentsOf: fileURL) no error is thrown and when I print it, it says 1024 bytes which matchs the size of the model (I am trialling this with just a very basic model). So I am guessing that means the fileURL is correct but the way I am trying to load the ARQuickLookPreviesItem is incorrect.

Add a Comment

Just in case anyone else stumbles upon the same issue I was able to fix it by replacing the last 5 lines in the previewController func with:

return fileUrl as QLPreviewItem