Using SpriteKit with SwiftUI
Tags: swiftui, spritekit, ios, uiviewrepresentable
Published: 2020-01-01 23:39:33

---
title: Using SpriteKit with SwiftUI
published: true
description: Adding an SKEmitterNode to a SwiftUI View
tags: swiftui, spritekit, iOS, UIViewRepresentable
cover_image: https://thepracticaldev.s3.amazonaws.com/i/54gq8a73pl91f0t0s546.PNG
---

### Background
About a month ago I decided to write a native iOS version of a [website](http://status.loudounfields.com) I'd launched in the Spring of 2019 that provides sports field closure status for the county I live in. The website is mobile friendly, of course, but I also wanted to provide additional functionality that my website doesn't, such as push notifications when the status of a favorite field changes and a "Today Widget" that gives users a quick glance at the status of their favorites.

Most importantly, it would satisfy my need for an excuse to learn `SwiftUI`.

The app will be free, but I thought giving users the ability to send me a few bucks if they've found the app useful couldn't hurt. If someone decided to do that, I wanted to be sure to show them a little love, too.

I've written a couple of games with `SpriteKit`, so I thought displaying a particle effect after a successful transaction would be fun, as well as  a nice way to say "thanks!".

But how can we use `SpriteKit` with `SwiftUI`?

### Enter UIViewRepresentable

`SwiftUI` provides a way to wrap `UIKit` and other framework components so they can be used in `SwiftUI` views via the `UIViewRepresentable` protocol. A common use case for this is wrapping `UIActivityIndicatorView` since `SwiftUI` doesn't provide it yet.

So, let's use `UIViewRepresentable` to make an `SKView` available to our `SwiftUI` view.


```swift
import SwiftUI
import SpriteKit

// EmitterView will be the name of our SwiftUI view.
struct EmitterView: UIViewRepresentable {
    /**
    makeUIView and updateUIView are required methods of the UIViewRepresentable
    protocol. makeUIView does just what you'd think - makes the view we want.

    updateUIView allows you to update the view with new data, but we don't need
    it for our purposes. 
    */
    func makeUIView(context: UIViewRepresentableContext<EmitterView>) -> SKView {
        // Create our SKView
        let view = SKView()
        // We want the view to animate the particle effect over our SwiftUI view
        // and let its components show through so we'll set allowsTransparenty to true.
        view.allowsTransparency = true
        // Load our custom SKScene
        if let scene = LoveScene(fileNamed: "LoveScene") {
            // We need to set the background to clear.
            scene.backgroundColor =  .clear
            view.presentScene(scene)
        }
        return view
    }

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

}

/**
This is our SKScene subclass that will present the emitter node.
*/
class LoveScene: SKScene {
    override func didMove(to view: SKView) {
       super.didMove(to: view)

       // Create our SKEmitterNode with a particle effect named "love"
       // More info about creating particle effects in Xcode is 
       // linked at the end of the article.
       if let emitter: SKEmitterNode = SKEmitterNode(fileNamed: "love") {
            // Set the initial alpha of the node to 0, as we're going to fade it in.
            emitter.alpha = 0
            // Add the emitter node to the scene
            addChild(emitter)
            // Fade the node in with a duration of half a second.
            emitter.run(SKAction.fadeIn(withDuration: 0.5)) {
                // Fade the node out with a duration of 5 seconds.
                emitter.run(SKAction.fadeOut(withDuration: 5.0)) {
                    // Clean up our emitter node.
                    emitter.removeFromParent()
                    // Tell our SwiftUI view that the animation has finished.
                    NotificationCenter.default.post(name: Utils.resetCoffeeNotification, object: nil)
                }
            }
        }
    }

}
```

### Using EmitterView in a SwiftUI View

In the `MenuView` of the app, there is an option to "Buy us a Cup of Coffee" that kicks off a $2.99 in-app-purchase request via `StoreKit`. When `StoreKit` informs us that the transaction was successful, we post a `notification` that the `MenuView` is listening for that updates an `@State` var named `coffee` to `true`.

When `coffee` is `true`, our `EmmitterView` is added to the view.

```swift
 @State private var coffee: Bool = false

 var body: some View {
        // We want the EmitterView to appear above the NavigationView
        // so we'll wrap them both in a ZStack
        ZStack {
            // coffee is a @State var, so the emitter view will only be rendered
            // to the view if its true.
            if coffee {
                EmitterView()
                    // We want the EmitterView to span the entire view, so we'll
                    // ignore safe areas.
                    .edgesIgnoringSafeArea(.all)
                    // The EmitterView needs to have a transparent background as well.
                    .background(Color.clear)
                    // Make sure we have a higher zIndex than the NavigationView
                    .zIndex(2)
                    // Disable the view so it doesn't eat taps while its visible.
                    .disabled(true)
            }
            NavigationView {
                VStack {
                    Form {
                        #if DEBUG
                        Section {
                            debug
                        }
                        #endif
                        Section {
                            mapPreference
                            notificationPreference
                            faq
                        }
                        ...
```

As pointed out in a comment in the `LoveScene` class, we also post a `notification` when the `SKEmitterNode` has completed its five second fade out. `MenuView` is listening for that as well and will set `coffee` back to `false` when it's received. 

To set that up, we'll add an `onAppear` handler on our `NavigationView` in the `MenuView` that adds the observers.

```swift
.onAppear {
   NotificationCenter.default.addObserver(forName: Utils.userDidBuyCoffeeNotification, object: nil, queue: nil, using: self.userDidBuyCoffee(_:))
   NotificationCenter.default.addObserver(forName: Utils.resetCoffeeNotification, object: nil, queue: nil, using: self.resetCoffee(_:))
}
```

Those two methods we register with `NotificationCenter` simply set `coffee` to `true` or `false`.

```swift
private func userDidBuyCoffee(_ notification:Notification) {
    coffee = true
}

private func resetCoffee(_ notification:Notification) {
    coffee = false
}

```

And with that, the user will see animated hearts dancing all over their screen after they've made a successful purchase:

<img src="https://thepracticaldev.s3.amazonaws.com/i/54gq8a73pl91f0t0s546.PNG" width="275" alt="A screenshot of the effect." />

### Conclusion
Pretty all right, yeah? The app will be launching in the next week or so - here's to hoping at least a couple of people see the particle effect in action!


### Additional Resources
- [Creating Particle Effects](https://developer.apple.com/documentation/spritekit/skemitternode/creating_particle_effects)
- [makeUIView(context:)](https://developer.apple.com/documentation/swiftui/uiviewrepresentable/3278049-makeuiview)
- [updateUIView(_:context:)](https://developer.apple.com/documentation/swiftui/uiviewrepresentable/3278050-updateuiview)
- [SKEmitterNode](https://developer.apple.com/documentation/spritekit/skemitternode?language=objc)
- [SKScene](https://developer.apple.com/documentation/spritekit/skscene)
- [SwiftUI Tutorials from Apple](https://developer.apple.com/tutorials/swiftui/)