You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:21:38