iosswiftsprite-kitsksceneswiftui

Using SpriteKit inside SwiftUI


I am having an issue when creating a SpriteKit scene within SwiftUI. I created this project initially as a SwiftUI project.

Here is the code I have so far:

ContentView.swift:

/// Where the UI content from SwiftUI originates from.
struct ContentView : View {
    var body: some View {
        // Scene
        SceneView().edgesIgnoringSafeArea(.all)
    }
}

SceneView.swift:

/// Creates an SKView to contain the GameScene. This conforms to UIViewRepresentable, and so can be used within SwiftUI.
final class SceneView : SKView, UIViewRepresentable {
    
    // Conformance to UIViewRepresentable
    func makeUIView(context: Context) -> SKView {
        print("Make UIView")
        return SceneView(frame: UIScreen.main.bounds)
    }
    func updateUIView(_ uiView: SKView, context: Context) {
        print("Update UIView")
    }
    
    // Creating scene
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        let scene = Scene(size: UIScreen.main.bounds.size)
        presentScene(scene)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Scene.swift:

/// The scene for the game in SpriteKit.
final class Scene : SKScene {

    override func didMove(to view: SKView) {
        super.didMove(to: view)
    
        print("Scene didMove:")
    }
}

Problem

The problem is that the scene is reloading multiple times, as shown by the logs (because there are prints in the code):

Scene didMove:

Make UIView

Scene didMove:

Update UIView


As you can see, Scene didMove: is printed twice. I only want this to be called once, as I want to create my sprites here. Any ideas?


Solution

  • iOS 14+

    There is now a native view responsible for displaying a SKScene - it's called SpriteView.

    Assuming we have a simple SKScene:

    class Scene: SKScene {
        ...
    }
    

    we can use a SpriteView to display it directly in a SwiftUI view:

    struct ContentView: View {
        var scene: SKScene {
            let scene = Scene()
            scene.size = CGSize(width: 300, height: 400)
            scene.scaleMode = .fill
            return scene
        }
    
        var body: some View {
            SpriteView(scene: scene)
                .frame(width: 300, height: 400)
                .edgesIgnoringSafeArea(.all)
        }
    }
    

    You can find more information here: