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

如何避免嵌套RealityViews中的不必要update调用

解决RealityView附件视图不必要的update调用问题

我有两个包含RealityView的视图First和Second,其中Second作为First的附件使用。First视图中声明了@State属性,当修改该状态时,SwiftUI会触发Second视图的update块(该状态并未在Second中使用)。如何避免Second中不必要的update块调用?

问题代码

import SwiftUI
import RealityKit

struct First: View {

    @State private var isButtonTapped = false

    var body: some View {
        print("Body #1")
        return VStack {
            RealityView { content, attach in
                let model = sphereModel(y: 0, color: .brown)
                content.add(model)
            } update: { content, attach in
                print(isButtonTapped)
                print("Update #1")
                guard let someView = attach.entity(for: "id")
                else { return }
                content.add(someView)
            } attachments: {
                Attachment(id: "id") {
                    Second()
                }
            }

            Button("BUTTON") { isButtonTapped.toggle() }
        }
    }
}

struct Second: View {

    var body: some View {
        print("Body #2")

        return RealityView { content in
            let model = sphereModel(y: 0, color: .red)
            content.add(model)
        } update: { content in
            print("Update #2")
        }
    }
}

func sphereModel(y: Float, color: UIColor) -> ModelEntity {
    let collisionComponent = CollisionComponent(
        shapes: [ShapeResource.generateSphere(radius: 0.01)]
    )

    let hoverEffectComponent = HoverEffectComponent(.highlight(.init(color: .blue)))

    var sphereModelMaterial = PhysicallyBasedMaterial()
    sphereModelMaterial.baseColor = .init(tint: color)

    let sphereMesh = MeshResource.generateSphere(radius: 0.01)

    let sphereModel = ModelEntity(
        mesh: sphereMesh,
        materials: [sphereModelMaterial]
    )

    sphereModel.transform.translation = .init(x: 0, y: y, z: 0)
    sphereModel.components.set(
        [
            collisionComponent,
            hoverEffectComponent
        ]
    )

    return sphereModel
}

解决方法

1. 让Second视图遵循Equatable并使用EquatableView包裹

SwiftUI默认会在父视图状态变化时刷新所有子视图(包括附件视图)。通过让Second遵循Equatable,并利用EquatableView,可以告诉SwiftUI只有当Second的依赖属性变化时才刷新它。因为Second没有任何外部依赖,直接返回true即可。

修改Second视图:

struct Second: View, Equatable {
    static func == (lhs: Second, rhs: Second) -> Bool {
        // 视图无外部依赖,始终视为相等
        true
    }

    // 原body代码不变
}

然后在First的附件中替换为:

Attachment(id: "id") {
    EquatableView(content: Second())
}

2. 优化First的RealityView update逻辑

原代码中First的update块每次都会执行content.add(someView),这会重复添加实体,不仅浪费性能,也可能间接导致附件视图刷新。修改成只在实体未添加时才执行:

RealityView { content, attach in
    let model = sphereModel(y: 0, color: .brown)
    content.add(model)
    // 首次加载时添加附件实体
    if let someView = attach.entity(for: "id") {
        content.add(someView)
    }
} update: { content, attach in
    print(isButtonTapped)
    print("Update #1")
    guard let someView = attach.entity(for: "id"), 
          !content.entities.contains(someView) else { return }
    content.add(someView)
}

修改后的完整代码

import SwiftUI
import RealityKit

struct First: View {

    @State private var isButtonTapped = false

    var body: some View {
        print("Body #1")
        return VStack {
            RealityView { content, attach in
                let model = sphereModel(y: 0, color: .brown)
                content.add(model)
                // 首次添加附件实体
                if let someView = attach.entity(for: "id") {
                    content.add(someView)
                }
            } update: { content, attach in
                print(isButtonTapped)
                print("Update #1")
                guard let someView = attach.entity(for: "id"), 
                      !content.entities.contains(someView) else { return }
                content.add(someView)
            } attachments: {
                Attachment(id: "id") {
                    EquatableView(content: Second())
                }
            }

            Button("BUTTON") { isButtonTapped.toggle() }
        }
    }
}

struct Second: View, Equatable {
    static func == (lhs: Second, rhs: Second) -> Bool {
        true
    }

    var body: some View {
        print("Body #2")

        return RealityView { content in
            let model = sphereModel(y: 0, color: .red)
            content.add(model)
        } update: { content in
            print("Update #2")
        }
    }
}

func sphereModel(y: Float, color: UIColor) -> ModelEntity {
    let collisionComponent = CollisionComponent(
        shapes: [ShapeResource.generateSphere(radius: 0.01)]
    )

    let hoverEffectComponent = HoverEffectComponent(.highlight(.init(color: .blue)))

    var sphereModelMaterial = PhysicallyBasedMaterial()
    sphereModelMaterial.baseColor = .init(tint: color)

    let sphereMesh = MeshResource.generateSphere(radius: 0.01)

    let sphereModel = ModelEntity(
        mesh: sphereMesh,
        materials: [sphereModelMaterial]
    )

    sphereModel.transform.translation = .init(x: 0, y: y, z: 0)
    sphereModel.components.set(
        [
            collisionComponent,
            hoverEffectComponent
        ]
    )

    return sphereModel
}

效果验证

修改完成后,点击按钮切换isButtonTapped状态时,只会打印Body #1和Update #1,Second的Body #2和Update #2不会再被不必要地触发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 15:29:50