Swift: Adding a SpriteKit Scene to a SwiftUI view (Bonus: isActive)
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
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()
}
}
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!