Animations are a great way to explain the functionality of our apps through motion, and to delight our users. Adding animations in the right places can really make a UI look a lot more polished and nice.

But creating great animations require quite a lot of tweaking and iterations, since we often need to experiment with different animation steps, durations and curves to get things to feel just right.

That's why it's so important to build animations using tools that enable us to easily change things and tweak parameters. This week, let's take a look at how SpriteKit can act as such a tool for certain kinds of animations.

This ad keeps all of Swift by Sundell free for everyone. If you can, please check this sponsor out, as that directly helps support this site: Instabug: Investigate, diagnose and resolve issues up to four times faster. Whether it’s a crash, slow screen transitions, slow network calls or unresponsive UIs, Instabug lets you utilize powerful performance patterns to trace the cause of each issue. Detect if a specific app version, device or network connection is affecting the user experience and spot trends and spikes. Get started now and ship apps that your users will love.

SpriteKit is Apple’s built-in framework (since iOS 7) for 2D game development. So while it’s primarily targeted towards making games, it’s also a really nice tool for any kind of 2D drawing and animations. In fact, at WWDC 2017, Apple revealed that they are actually using SpriteKit to build the UI for the memory debugger in Xcode.

For animations, SpriteKit can be really useful when building more complex, self-contained scenes. For example, when you are creating some form of full screen loading animation, an illustration as part of your onboarding flow, or anything else that includes multiple animation steps and doesn’t involve directly animating views and UI controls.

As an example, we’re going to build a loading animation containing 4 emojis, that's going to look like this:

All SpriteKit content is presented in a scene, managed by an instance of the SKScene class. Content is then defined using a node based system, that enables you to create hierarchies, just like when using UIView s or CALayer s.

You create nodes using various subclasses of SKNode , for example SKSpriteNode for sprite (image) based content, or SKLabelNode for textual content.

Finally, you use actions (represented by the SKAction class) to make your nodes perform various animations (such as moving, scaling, rotating, etc) in your scene.

Let’s start by creating an SKScene as a container for our animation. We give it a square size taken from the minimum dimension of a view controller’s view, and set a white background color:

extension AnimationViewController { func makeScene() -> SKScene { let minimumDimension = min (view. frame . width , view. frame . height ) let size = CGSize (width: minimumDimension, height: minimumDimension) let scene = SKScene (size: size) scene. backgroundColor = . white return scene } }

You present an SKScene using an SKView (which is a UIView subclass on iOS). We’ll add such a view to our view controller, and set its size and center point. Finally, we tell it to present our scene, like this:

class AnimationViewController: UIViewController { private lazy var animationView = SKView () override func loadView() { super . loadView () view. addSubview (animationView) } override func viewWillAppear( _ animated: Bool ) { super . viewWillAppear (animated) guard animationView. scene == nil else { return } let scene = makeScene () animationView. frame . size = scene. size animationView. presentScene (scene) } override func viewDidLayoutSubviews() { super . viewDidLayoutSubviews () animationView. center . x = view. bounds . midX animationView. center . y = view. bounds . midY } }

So we now have a scene to render in, and a view that will present it in our view controller. Let’s start adding some content. If you were rendering images and animations using keyframes, you’d want to use SKSpriteNode for rendering. But in this example we’ll stick with some simple emojis, so we’re going to use SKLabelNode (which is basically the SpriteKit equivalent of UILabel ).

Let’s start by creating an extension on SKLabelNode , which lets us render an emoji:

extension SKLabelNode { func renderEmoji( _ emoji: Character ) { fontSize = 50 text = String (emoji) verticalAlignmentMode = . center horizontalAlignmentMode = . center } }

We’ll then create another extension method on our view controller to add all emoji our scene (which we’ll call from makeScene() ):

extension AnimationViewController { func addEmoji(to scene: SKScene ) { let allEmoji: [ Character ] = [ "🌯" , "🌮" , "🍔" , "🍕" ] let distance = floor (scene. size . width / 4 ) for (index, emoji) in allEmoji. enumerated () { let node = SKLabelNode () node. renderEmoji (emoji) node. position . y = floor (scene. size . height / 2 ) node. position . x = distance * ( CGFloat (index) + 0.5 ) scene. addChild (node) } } }

With all the setup out of the way, let’s get to the fun part - actually creating our animation. We’ll perform the animation by telling each emoji node to scale up and then back down, with a slight delay depending on the index of the node:

func animateNodes( _ nodes: [ SKNode ]) { for (index, node) in nodes. enumerated () { let delayAction = SKAction . wait (forDuration: TimeInterval (index) * 0.2 ) let scaleUpAction = SKAction . scale (to: 1.5 , duration: 0.3 ) let scaleDownAction = SKAction . scale (to: 1 , duration: 0.3 ) let waitAction = SKAction . wait (forDuration: 2 ) let scaleActionSequence = SKAction . sequence ([scaleUpAction, scaleDownAction, waitAction]) let repeatAction = SKAction . repeatForever (scaleActionSequence) let actionSequence = SKAction . sequence ([delayAction, repeatAction]) node. run (actionSequence) } }

The above code works, but is quite hard to read and reason about, especially if we were to take away the comments. But the good news is that we can easily fix it! Thanks to Swift’s awesome dot notation syntax, we can heavily reduce the verbosity of our code, and get rid of all the temporary let assignments:

extension AnimationViewController { func animateNodes( _ nodes: [ SKNode ]) { for (index, node) in nodes. enumerated () { node. run (. sequence ([ . wait (forDuration: TimeInterval (index) * 0.2 ), . repeatForever (. sequence ([ . scale (to: 1.5 , duration: 0.3 ), . scale (to: 1 , duration: 0.3 ), . wait (forDuration: 2 ) ])) ])) } } }

We can now start our animation by calling the above method with all of our scene's nodes in makeScene() :

animateNodes (scene. children )

Now that we have easy to read (and very declarative) animation code, we can start to play around with it. Let’s say we wanted to add a little twist (literally!) to our animation, by making our emoji rotate 360 degrees at the same time as they’re scaling up and down.

All we have to do to make this happen is to combine the scaling actions and a rotation action into a group, like this:

extension AnimationViewController { func animateNodes( _ nodes: [ SKNode ]) { for (index, node) in nodes. enumerated () { node. run (. sequence ([ . wait (forDuration: TimeInterval (index) * 0.2 ), . repeatForever (. sequence ([ . group ([ . sequence ([ . scale (to: 1.5 , duration: 0.3 ), . scale (to: 1 , duration: 0.3 ) ]), . rotate (byAngle: . pi * 2 , duration: 0.6 ) ]), . wait (forDuration: 2 ) ])) ])) } } }

Which will give us the following result:

Support Swift by Sundell by checking out this sponsor:

Instabug: Investigate, diagnose and resolve issues up to four times faster. Whether it’s a crash, slow screen transitions, slow network calls or unresponsive UIs, Instabug lets you utilize powerful performance patterns to trace the cause of each issue. Detect if a specific app version, device or network connection is affecting the user experience and spot trends and spikes. Get started now and ship apps that your users will love.

As you can see in the example above, using SpriteKit for animations enables us to write very declarative animation code that is easy to change, extend and experiment with. It’s definitely not a silver bullet for animations, since we have no way of animating any form of UIView s this way, but for self-contained scene based animations I think it’s a super nice tool.

There’s of course a lot more to SpriteKit than what I was able to cover in this post, so please let me know if you’d like me to do another post that goes more in-depth on SpriteKit and more of its capabilities (like physics, interactions, etc).

What do you think? Will you try using SpriteKit the next time you need to build a more custom animation, like the one in this post? Let me know, along with any other questions or feedback that you might have, on Twitter @johnsundell.

Thanks for reading! 🚀