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

如何在QLPreviewController中动态修改RealityKit实体文本并展示

能否在QLPreviewController中动态修改Reality Composer场景的实体文本?

之前在Stack Overflow上看到过关于在Reality Composer场景中动态修改实体文本的优质问答,现在想了解是否可以在QLPreviewController而非ARView中实现相同功能?

我当前的代码是从URL加载预览项并载入QLPreviewController,希望在返回预览项进行展示前,动态修改.rcproject文件中包含的气泡文本,代码如下:

class ARQLViewController: UIViewController, 
                          QLPreviewControllerDataSource, 
                          QLPreviewControllerDelegate {

    var allowsContentScaling = true
    var webViewModel: WebViewModel

    init(webViewModelReceived: WebViewModel) {
        webViewModel = webViewModelReceived
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidAppear(_ animated: Bool) {
        setupOnAppear()
    }

    func setupOnAppear() {
        let previewController = QLPreviewController()
        previewController.dataSource = self
        previewController.delegate = self
        present(previewController, animated: true, completion: nil)
    }

    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
        return 1
    }

    func previewController(_ controller: QLPreviewController, 
                    previewItemAt index: Int) -> QLPreviewItem {
        let url = webViewModel.afUrlForAR  
        let previewItem = ARQuickLookPreviewItem(fileAt: url!)
        previewItem.allowsContentScaling = allowsContentScaling
        return previewItem
    }
}

解决方案

QLPreviewController本身无法直接修改内存中的Reality Composer场景内容,它是基于文件路径直接加载展示的。要实现动态修改文本的需求,必须先修改本地文件的内容,再将修改后的文件传给预览控制器,具体步骤如下:

核心思路

  1. 复制原.rcproject文件到临时目录:原文件可能处于只读状态(比如来自App Bundle或网络下载),需要复制到App沙盒的可写临时目录。
  2. 修改临时文件中的文本内容:.rcproject本质是一个Bundle包,内部包含存储场景数据的plist文件,找到对应文本节点修改值。
  3. 用修改后的临时文件创建预览项:将临时文件路径传入ARQuickLookPreviewItem,再返回给QLPreviewController。

修改后的代码示例

class ARQLViewController: UIViewController, QLPreviewControllerDataSource, QLPreviewControllerDelegate {

    var allowsContentScaling = true
    var webViewModel: WebViewModel

    init(webViewModelReceived: WebViewModel) {
        webViewModel = webViewModelReceived
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidAppear(_ animated: Bool) {
        setupOnAppear()
    }

    func setupOnAppear() {
        let previewController = QLPreviewController()
        previewController.dataSource = self
        previewController.delegate = self
        present(previewController, animated: true, completion: nil)
    }

    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
        return 1
    }

    func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
        guard let originalUrl = webViewModel.afUrlForAR else {
            fatalError("AR file URL is nil")
        }
        
        // 1. 复制原文件到临时目录
        let tempDir = FileManager.default.temporaryDirectory
        let tempFileName = originalUrl.lastPathComponent
        let tempUrl = tempDir.appendingPathComponent(tempFileName)
        
        do {
            // 清理已存在的临时文件
            if FileManager.default.fileExists(atPath: tempUrl.path) {
                try FileManager.default.removeItem(at: tempUrl)
            }
            // 复制原文件到临时目录
            try FileManager.default.copyItem(at: originalUrl, to: tempUrl)
            
            // 2. 修改临时文件中的文本内容
            modifyRCProjectText(at: tempUrl, newText: "你的动态文本内容")
            
        } catch {
            print("文件操作失败: \(error.localizedDescription)")
        }
        
        // 3. 使用修改后的文件创建预览项
        let previewItem = ARQuickLookPreviewItem(fileAt: tempUrl)
        previewItem.allowsContentScaling = allowsContentScaling
        return previewItem
    }
    
    /// 修改.rcproject包内的文本内容
    private func modifyRCProjectText(at projectUrl: URL, newText: String) {
        // 获取rcproject包内的场景plist文件
        let projectBundle = Bundle(url: projectUrl)
        guard let scenePlistPath = projectBundle?.path(forResource: "Scene", ofType: "plist") else {
            print("找不到Scene.plist文件")
            return
        }
        
        // 读取并解析plist数据
        guard var sceneData = NSMutableDictionary(contentsOfFile: scenePlistPath),
              var entities = sceneData["entities"] as? [[String: Any]] else {
            print("无法读取场景数据")
            return
        }
        
        // 遍历实体,找到文本组件并修改内容
        for (index, var entity) in entities.enumerated() {
            if var components = entity["components"] as? [[String: Any]] {
                for (compIndex, var component) in components.enumerated() {
                    if component["type"] as? String == "text",
                       var properties = component["properties"] as? [String: Any] {
                        // 更新文本值
                        properties["text"] = newText
                        component["properties"] = properties
                        components[compIndex] = component
                        entity["components"] = components
                        entities[index] = entity
                    }
                }
            }
        }
        
        // 将修改后的数据写回plist
        sceneData["entities"] = entities
        sceneData.write(toFile: scenePlistPath, atomically: true)
    }
}

注意事项

  • .rcproject的内部结构可能随Reality Composer版本变化,需要根据你自己的场景实际结构调整modifyRCProjectText方法中的遍历逻辑。
  • 确保文件操作处理好异常情况,避免因权限问题或文件损坏导致预览失败。
  • 如果原文件来自网络,要确保文件完全下载完成后再执行复制和修改操作。

内容的提问来源于stack exchange,提问作者D. Rothschild

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 04:00:52