简体   繁体   中英

Initialize materials on scene change in SceneKit (SwiftUI)

I'm using SceneKit in my SwiftUI app to display some .scn files converted from .dae files exported from Blender.

The SceneKit is being displayed using UIViewRepresentable .

I'm displaying a step by step instructional guide, and each step contains some text and an animation. Each animation is its own .scn file and I'm swapping the scene file based on the current step.

My issue is I need to perform some initialisation of materials when the scene is swapped but only updateUIView is called when this happens, and I don't want to be doing this work here as this gets called each time the animation is paused etc.

I want to be doing this work once each time the scene is replaced but I'm struggling to find a solution to this with my current implementation.

I'm thinking the way I'm handling this scene swapping is incorrect but I'm wondering if someone can point me in the right direction.

Thanks!

See full code below:

struct MaintenanceFlowDetailView: View {

    var maintenanceFlow: MaintenanceFlow
    @State private var currentStepIndex = 0
    @State private var pauseAnimation = false

    func stepBackwards() {
        currentStepIndex -= 1
        pauseAnimation = false
    }

    func stepForwards() {
        currentStepIndex += 1
        pauseAnimation = false
    }

    func togglePauseAnimation() {
        pauseAnimation = !pauseAnimation
    }

    var body: some View {
        NavigationView{
            VStack(alignment: .leading, spacing: 12){
                SceneKitView(sceneFilePath: maintenanceFlow.steps[currentStepIndex].scenePath, pauseAnimation: $pauseAnimation)
                    .frame(height: 300)
                Text(maintenanceFlow.steps[currentStepIndex].text)
                Spacer()
                HStack{
                    if currentStepIndex > 0 {
                        Button(action: stepBackwards) {
                            Text("Back")
                        }
                    }
                    Spacer()
                    Button(action: togglePauseAnimation) {
                        Text(pauseAnimation ? "Play" : "Pause")
                    }
                    Spacer()
                    if currentStepIndex < maintenanceFlow.steps.count - 1 {
                        Button(action: stepForwards) {
                            Text("Next")
                        }
                    }

                }
            }.padding()
            .navigationBarTitle(Text(maintenanceFlow.name))
        }

    }
}

struct SceneKitView : UIViewRepresentable {

    var sceneFilePath: String
    @Binding var pauseAnimation: Bool

    func makeUIView(context: Context) -> SCNView {

        print("calling CREATE view")

        // I NEED TO INITIALISE MATERIALS ETC JUST ONCE HERE...

        let scnView = SCNView()
        return scnView
    }

    func updateUIView(_ scnView: SCNView, context: Context) {
        print("calling UPDATE view")

        // BUT THE SCENE IS REPLACED HERE :(

        let scene = SCNScene(named: sceneFilePath)!
        scene.isPaused = pauseAnimation
        scnView.scene = scene

        scnView.showsStatistics = true
    }
}

Use the following:

struct SceneKitView : UIViewRepresentable {

    var sceneFilePath: String
    @Binding var pauseAnimation: Bool

    func makeUIView(context: Context) -> SCNView {

        print("calling CREATE view")

        let scnView = SCNView()
        let scene = SCNScene(named: sceneFilePath)

        scnView.scene = scene
        scnView.showsStatistics = true
        return scnView
    }

    func updateUIView(_ scnView: SCNView, context: Context) {
        print("calling UPDATE view")
        scnView.scene?.isPaused = pauseAnimation
    }
}

Update: a bit corrected, but idea is the same. Tested with Xcode 11.4 / iOS 13.4. SceneKitView is recreated when sceneFilePath: is changed, so if it is not updated, then it is different code issue.

Here is testing view used:

struct TestSceneKitView: View {
    @State private var filePath = "test1"
    var body: some View {
        VStack {
            Button("Push") { self.filePath = "test2" }
            SceneKitView(sceneFilePath: filePath, pauseAnimation: .constant(false))
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM