Swift: Adding a SpriteKit Scene to a SwiftUI view (Bonus: isActive)

Rinaldo Jr
Apple Developer Academy | UFPE
5 min readMay 6, 2023

In this article I’ll show you how to place a SpriteKit Scene inside a SwiftUI view, in a very easy way!

Adding a Scene to the Project

1. Create the GameScene file

2. Implement the Scene

For this example we'll make our GameScene have an Static red Square, just for didactic purposes.

class GameScene: SKScene {
var redSquare = SKSpriteNode(color: .red, size: CGSize(width: 200, height: 200)) // create node and set it's size

override func didMove(to view: SKView) {
setup()
}

func setup() {
redSquare.position = CGPoint(x: self.frame.midX, y: self.frame.midY) // middle of the Scene
addChild(redSquare) // place the node in the scene
}
}

3. Go to the ContentView (or were you are working on)

In the view you may instantiate the Scene so you can use and show it in the View

var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: 1000, height: 1000)
scene.scaleMode = .aspectFill //or .aspectFit
return scene
}

Note that you have to give a size to the scene, so if you want the size to be flexible a tip is you to use or geometry reader or UIScreen.main.bounds.width and .height

4. Make a SpriteView in your ContentView (or in the view you are working on)

Just like this:

struct ContentView: View {
var body: some View {
SpriteView(scene: scene)
}
}

if you did everything correct your preview may look like this:

Here is all the code needed in the ContentView file;

import SwiftUI
import SpriteKit

struct ContentView: View {
var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: 4000, height: 3000)
scene.scaleMode = .aspectFill //or .aspectFit
return scene
}

var body: some View {
SpriteView(scene: scene)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

in the GameScene file;

import SpriteKit

class GameScene: SKScene {
var redSquare = SKSpriteNode(color: .red, size: CGSize(width: 200, height: 200)) // create node and set it's size

override func didMove(to view: SKView) {
setup()
}

func setup() {
redSquare.position = CGPoint(x: self.frame.midX, y: self.frame.midY) // middle of the Scene
addChild(redSquare) // place the node in the scene
}
}

Bonus: Using isActive to trigger navigation inside the GameScene

Here is a bonus apprenticeship I would like to share, in this example we’ll make the red Square inside the GameScene clickable and if you click you navigate to another view (this is very useful if you want to have navigation in your game).

1. Create the ContentView2 file as a SwiftUI View

(I also changed the text for further identification)

2. Make the red square clickable

To turn it into a clickable node we’ll first make a function to identify if the user is clicking the scene and in which node is being clicked.

To identify when and where the user touched we’ll use a SpriteKit native function called touchesBegan:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: UITouch in touches {
let location = touch.location(in: self)
}
}

Now have to update te setup function to give the redSquare a name so we can know which node is being clicked:

func setup() {
redSquare.position = CGPoint(x: self.frame.midX, y: self.frame.midY) // middle of the Scene
redSquare.name = "redSquare"
addChild(redSquare) // place the node in the scene
}

Now only have to use the name in the touchesBegan, just like this:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: UITouch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
let nodeName = touchedNode.name

if nodeName == "redSquare" {
print("clicked the red square")
}
}
}

3. Adapt your GameScene to receive a Binding<Bool> and the size

import SpriteKit
import SwiftUI

class GameScene: SKScene {

@Binding var didClick : Bool

public init(didClick: Binding<Bool>, size: CGSize) {
// Gets the values from the view
_didClick = didClick
super.init(size: size)
}

public required init?(coder aDecoder: NSCoder) {
// Catch error
fatalError()
}

var redSquare = SKSpriteNode(color: .red, size: CGSize(width: 200, height: 200)) // create node and set it's size

override func didMove(to view: SKView) {
setup()
}

func setup() {
redSquare.position = CGPoint(x: self.frame.midX, y: self.frame.midY) // middle of the Scene
redSquare.name = "redSquare"
addChild(redSquare) // place the node in the scene
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: UITouch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
let nodeName = touchedNode.name

if nodeName == "redSquare" {
didClick = true
print(didClick)
}
}
}
}

It may look very complex at first but basically we needed a init so further in the application we can use it to give to the GameScene values from the View in this case the size and the bool, if you struggle to understand why using a @Biding also about property wrapper (You can read this article to understand better about it)

4. Make a NavigationLink in the View and @State didClick

To navigate in between views you need to create a NavigationLink that when triggered pushes the other view up. The trick is, we can have a invisible NavigationLink and use a boolean variable that when true it triggers, for that effect we’ll use the isActive:

As we made changes to the way we initialize the GameScene we may as Well update that too.

import SwiftUI
import SpriteKit

struct ContentView: View {

@State var didClick = false

var scene: SKScene {
let size = CGSize(width: 4000, height: 3000)
let scene = GameScene(didClick: $didClick, size: size)
scene.scaleMode = .aspectFill //or .aspectFit
return scene
}

var body: some View {
NavigationView{
ZStack{
NavigationLink("", destination: ContentView2(),isActive: $didClick)
SpriteView(scene: scene)
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The End Result 🤩🎉

Final Considerations:

If you liked this article consider following me here and on my instagram https://www.instagram.com/rinaldo_sbj/ as well as my GitHub https://github.com/RinaldoJr4. Good studies!

--

--