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

iOS Swift ARKit动态更新SCNText三维文本问题求助

解决iOS Swift AR应用中动态更新3D文本及节点移除的问题

看起来你遇到了两个核心问题:一是无法实现3D文本的周期性计数更新,二是移除节点时触发的EXC_BAD_ACCESS错误。咱们一步步来拆解和解决:

问题根源分析

先看你当前的updateSCNText2方法和渲染循环代码,主要问题有这几点:

  • 节点添加与移除顺序错误:你先把新创建的同名节点添加到场景,再去查找并移除同名节点——这时候移除的其实是刚添加的新节点,导致屏幕上根本看不到文本(或者一闪而过)。
  • 不必要的节点重复创建:每帧都创建新的SCNText、SCNMaterial和SCNNode,不仅会造成严重的内存浪费,还会触发频繁的场景树更新,影响AR性能。
  • 移除节点时的野指针风险:在渲染循环的高频调用中,节点可能还未完全被场景树引用就被尝试移除,导致EXC_BAD_ACCESS错误。
  • 计数速度失控:renderer(_:updateAtTime:)每帧都会调用一次,k会瞬间递增到10000,你根本看不到计数的动态过程。

最优解决方案:复用节点,直接更新文本内容

最合理的做法是只初始化一次3D文本节点,之后每次更新只修改文本的内容,完全避免节点的添加和移除操作。具体步骤如下:

1. 声明全局节点属性

在你的ViewController类中添加一个属性,用来保存文本节点的引用:

class ViewController: UIViewController, ARSCNViewDelegate {
    @IBOutlet var sceneView: ARSCNView!
    // 保存文本节点的引用
    private var textNode: SCNNode?
    private var currentCount = 0
    // 控制计数速度,比如每0.1秒递增一次
    private var lastUpdateTime: TimeInterval = 0
    // ... 其他代码
}

2. 在viewDidLoad中初始化静态文本节点

把你原来的静态文本初始化代码移到这里,只执行一次:

override func viewDidLoad() {
    super.viewDidLoad()
    sceneView.delegate = self
    
    // 初始化3D文本
    let text = SCNText(string: "Let's begin!", extrusionDepth: 1)
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.green
    text.materials = [material]
    
    // 创建节点并设置属性
    textNode = SCNNode(geometry: text)
    textNode?.scale = SCNVector3(x: 0.004, y: 0.004, z: 0.004)
    textNode?.position = SCNVector3(x: 0, y: 0.02, z: -0.5)
    
    // 添加到场景
    if let textNode = textNode {
        sceneView.scene.rootNode.addChildNode(textNode)
    }
}

3. 在渲染循环中更新文本内容

修改renderer(_:updateAtTime:)方法,只更新文本的string属性,同时控制计数速度:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    // 控制每0.1秒递增一次,避免计数过快
    guard time - lastUpdateTime > 0.1 else { return }
    lastUpdateTime = time
    
    // 计数到10000后停止
    guard currentCount < 10000 else { return }
    currentCount += 1
    
    // 更新3D文本内容
    if let textGeometry = textNode?.geometry as? SCNText {
        textGeometry.string = String(currentCount)
        // 可选:如果文本宽度变化导致位置偏移,可以重新调整节点位置
        // textNode?.position = ...
    }
}

4. 为什么这个方法能解决问题?

  • 避免节点添加/移除操作:复用同一个节点,完全消除了节点查找和移除的风险,也就不会触发EXC_BAD_ACCESS错误。
  • 性能更优:不需要重复创建几何对象和材质,减少了内存占用和场景树的更新开销。
  • 可控的计数速度:通过时间间隔控制计数频率,让你能清晰看到从0到10000的动态过程。

如果你一定要用移除节点的方式(不推荐)

如果因为某些原因必须每次创建新节点,那你需要调整添加和移除的顺序,并且确保节点引用的安全性:

func updateSCNText2(incomingInt: Int) {
    // 先移除旧节点
    sceneView.scene.rootNode.childNode(withName: "textNodeName", recursively: false)?.removeFromParentNode()
    
    // 创建新节点
    let text = SCNText(string: String(incomingInt), extrusionDepth: 1)
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.green
    text.materials = [material]
    
    let textNode = SCNNode(geometry: text)
    textNode.name = "textNodeName"
    textNode.scale = SCNVector3(x: 0.004, y: 0.004, z: 0.004)
    textNode.position = SCNVector3(x: 0, y: 0.02, z: -0.5)
    
    // 添加新节点
    sceneView.scene.rootNode.addChildNode(textNode)
}

注意:这种方法依然存在性能问题,而且在高频调用下还是可能出现节点引用的问题,所以优先推荐第一种复用节点的方案。

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:07:23