How can I get SKEmitterNode to work in SwiftUI?

徘徊边缘 提交于 2021-01-28 00:35:43

问题


I'm trying to update the colour of an SKEmitterNode within a UIViewRepresentable. The hue value is passed in from the state on the parent View, and the emitter colour should update when the hue value in the parent state updates.

It initially displays, and although it updates on the first call to updateUIView it does not respond to any subsequent calls, even though the function definitely gets called with the new value of hue each time.

Has anyone any idea why the emitter won't update? I'm at the hair-tearing-out stage...

  import SwiftUI
  import UIKit
  import SpriteKit

  struct EmitterView: UIViewRepresentable {
    private let view = SKView()

    let scene = SKScene(fileNamed: "myScene")!
    var emitter: SKEmitterNode = SKEmitterNode()
    var hue: Double

    func makeUIView(context: UIViewRepresentableContext<EmitterView>) -> SKView {

      // Lets make it manually
      emitter.particleTexture = SKTexture(imageNamed: "spark")
      emitter.particleBirthRate = 80
      emitter.particleLifetime = 2.5
      emitter.particlePositionRange = CGVector(dx: 200, dy: 150)
      emitter.particleScale = 0.2
      emitter.particleScaleSpeed = 0.45
      emitter.particleColor = SKColor.blue
      emitter.particleColorBlendFactor = 1.0

      scene.addChild(emitter)
      view.presentScene(scene)

      return view
    }

    func updateUIView(_ uiView: SKView, context: UIViewRepresentableContext<EmitterView>) {

      let color: SKColor = SKColor(
        hue: CGFloat(hue),
        saturation: 1.0,
        brightness: 1.0,
        alpha: 1.0
      )

      print("Hue is now", hue)

      emitter.resetSimulation()
      emitter.particleColor = color
    }
  }

  struct EmitterView_Previews: PreviewProvider {
    static var previews: some View {
      EmitterView(hue: 0.5)
    }
  }

回答1:


Now that 11 is out I can finally play with Swift UI.

The problem being experienced is that your UIViewRepresentable is a struct.

The way structs work is they are copy on write.

I am going to assume that in your scene delegate, you are not reassigning the struct in your root controller when you set the hue.

You need to move your code into class for best results (Not the coordinator, they are used for application flow. Research Coordinator Pattern for more info.)

The best class would be your SKScene.

I recommend making a GameScene class and changing your sks file to point to it.

You can then create a function to this game scene class that will allow you to alter the emitter without altering the struct.

import SwiftUI
import UIKit
import SpriteKit

struct EmitterView: UIViewRepresentable {
    private let view = SKView()
    let scene = GameScene(fileNamed: "myScene")!
    var hue : Double{
        get{
            return scene.hue
        }
        set{
            scene.hue = newValue
        }
    }
    func makeUIView(context: UIViewRepresentableContext<EmitterView>) -> SKView {
        view.presentScene(scene)
        return view
    }

    func updateUIView(_ uiView: SKView, context: UIViewRepresentableContext<EmitterView>) {


    }
}

struct EmitterView_Previews: PreviewProvider {
    static var previews: some View{
          EmitterView()
    }
}


class GameScene : SKScene{
    var hue : Double = 0{
        didSet{
            let color: SKColor = SKColor(
                hue: CGFloat(hue),
                saturation: 1.0,
                brightness: 1.0,
                alpha: 1.0
            )

            print("Hue is now", hue)

            emitter.resetSimulation()
            emitter.particleColor = color
        }
    }

    var emitter: SKEmitterNode = SKEmitterNode()
    override func didMove(to view: SKView) {
        emitter.particleTexture = SKTexture(imageNamed: "spark")
        emitter.particleBirthRate = 80
        emitter.particleLifetime = 2.5
        emitter.particlePositionRange = CGVector(dx: 200, dy: 150)
        emitter.particleScale = 0.2
        emitter.particleScaleSpeed = 0.45
        emitter.particleColor = SKColor.blue
        emitter.particleColorBlendFactor = 1.0
        addChild(emitter)
    }

}

The reason why this works is because on copy, the view will copy the pointer, where as hue will copy the value.

If you need to do things in the updateUIView, you can always cast uiView.scene to GameScene and access it that way.




回答2:


In order to make the UIViewRepresentable struct mutable, add the @Binding prefix to the hue variable.

@Binding var hue: Double

You can then change the color of the particles from a parent View in the following manner:

struct ContentView: View {
    @State var hue: Double = 0.5
    var body: some View {
        VStack {
            EmitterView(hue: self.$hue)
            Slider(value: $hue)
        }
    }
}


来源:https://stackoverflow.com/questions/57948554/how-can-i-get-skemitternode-to-work-in-swiftui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!