ARKit开发疑难:如何获取SCNScene URL的下载进度
解决ARKit中无法获取SCNScene URL下载进度的问题
嘿,我来帮你搞定这个问题!你现在的代码没法获取下载进度,核心原因是直接用SCNScene的默认加载方式(比如SCNScene(url:))没有提供进度回调接口。咱们得换个思路:先通过URLSessionDownloadTask手动下载模型文件,拿到实时进度后,再把下载好的文件加载成SCNScene,这样就能在弹窗里动态显示进度啦!
第一步:定义必要的属性
先在你的ViewController里加几个属性,用来保存下载任务、进度值和加载弹窗:
var downloadTask: URLSessionDownloadTask? var downloadProgress: Float = 0.0 var loadingAlert: UIAlertController?
第二步:改造下载方法
把原来的downloadModel方法改成用URLSession下载,同时初始化并显示加载弹窗:
func downloadModel(hitTestResult: ARHitTestResult) { // 替换成你实际的模型文件URL guard let modelURL = URL(string: "https://your-model-url-here.scn") else { print("无效的模型URL") return } // 创建加载弹窗 loadingAlert = UIAlertController(title: nil, message: "Please wait...0%", preferredStyle: .alert) let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) loadingIndicator.style = .large loadingIndicator.startAnimating() loadingIndicator.hidesWhenStopped = true loadingAlert?.view.addSubview(loadingIndicator) // 主线程显示弹窗(UI操作必须在主线程) DispatchQueue.main.async { self.present(self.loadingAlert!, animated: true) } // 创建带代理的URLSession,用来追踪下载进度 let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue()) downloadTask = session.downloadTask(with: modelURL) downloadTask?.resume() }
第三步:实现URLSession代理方法获取进度
让你的ViewController遵循URLSessionDownloadDelegate协议,然后实现三个关键代理方法,分别处理进度更新、下载完成和下载失败:
extension YourViewController: URLSessionDownloadDelegate { // 实时获取下载进度 func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { // 计算进度百分比 downloadProgress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite) let progressPercentage = Int(downloadProgress * 100) // 主线程更新弹窗消息 DispatchQueue.main.async { self.loadingAlert?.message = "Please wait...\(progressPercentage)%" } } // 下载完成后处理文件并加载SCNScene func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { // 把临时下载文件复制到沙盒的文档目录(临时文件会被系统自动删除) let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let destURL = documentsDir.appendingPathComponent(downloadTask.response?.suggestedFilename ?? "model.scn") do { // 如果目标路径已有文件,先删除 if FileManager.default.fileExists(atPath: destURL.path) { try FileManager.default.removeItem(at: destURL) } // 复制临时文件到目标路径 try FileManager.default.copyItem(at: location, to: destURL) // 主线程关闭弹窗并加载场景 DispatchQueue.main.async { self.loadingAlert?.dismiss(animated: true) { if let scene = SCNScene(url: destURL) { // 这里把加载好的场景添加到AR视图中 let modelNode = SCNNode() modelNode.addChildNodes(scene.rootNode.childNodes) // 利用hitTestResult的位置信息放置模型 modelNode.transform = hitTestResult.worldTransform self.arView.scene.rootNode.addChildNode(modelNode) } else { print("加载SCNScene失败") } } } } catch { print("复制文件出错:\(error.localizedDescription)") DispatchQueue.main.async { self.loadingAlert?.dismiss(animated: true) } } } // 处理下载失败的情况 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { print("下载失败:\(error.localizedDescription)") DispatchQueue.main.async { self.loadingAlert?.dismiss(animated: true) // 显示错误提示弹窗 let errorAlert = UIAlertController(title: "Error", message: "Failed to download model", preferredStyle: .alert) errorAlert.addAction(UIAlertAction(title: "OK", style: .default)) self.present(errorAlert, animated: true) } } } }
几个注意事项
- 记得把
YourViewController替换成你实际的ViewController类名,并且确保它遵循了URLSessionDownloadDelegate协议 - 一定要替换代码里的模型URL为你实际的文件地址
- 所有UI相关的操作(比如更新弹窗、显示/关闭弹窗)必须放在主线程,否则会崩溃
- 如果需要支持取消下载,可以给弹窗加个取消按钮,点击时调用
downloadTask?.cancel()
内容的提问来源于stack exchange,提问作者Duc Phan
相关产品推荐
相关产品推荐

