watchOS 3 introduced the ability to include SpriteKit and SceneKit scenes inside of your watchOS projects. At the same time, Apple also introduced more animation capabilities with the standard WatchKit UI elements, but the power of Core Animation that iOS developers have become accustomed to was missing in watchOS.
Fortunately, watchOS does have more animation abilities, but it relies on SpriteKit and SceneKit to perform those animations. In this article, we’ll show you exactly how to create your own loading spinner using SpriteKit and integrate it into a watchOS project.
Building the Project
The first step requires that we create a watchOS app. If you already have an existing project you want to add this custom progress indicator to, then skip ahead to the next section.
Create a new watchOS project by performing the following steps:
- Open Xcode
- Select File | New | Project
- Select the watchOS tab, then “iOS App with WatchKit App” and click Next
- Name your project, then ensure that the language is set to “Swift” before saving the project files
Adding a SpriteKit scene
Now that we have a project to work with, let’s start by adding a SpriteKit scene file to the project:
- Select File | New | File
- Select the “SpriteKit Scene” file type, then click Next
- Add it to the WatchKit Extension target, and name it “ProgressIndicatorScene”
When you open this file, you’ll notice that it’s similar to a Storyboard scene, with an object library in the right-hand sidebar that allows you to drop SpriteKit objects into the scene and build a SpriteKit scene.
The first step is to click the “Scene” in the Scene Graph View and open the Attributes Inspector. Set the Size value to “Apple Watch,” and then select your background color. Here, we’ll stick with a black background per Apple Watch standards.
Next, drag and drop a “Color Sprite” object from the object library to the scene. Select it, then open the Attributes Inspector. In this inspector, set the Texture to be the progress image you wish to use. The texture can be a PNG or PDF vector image. Next, set the position values to X = 0, Y = 0; and make the size values W = 100, H = 100.
Next, drag and drop a “Camera” object from the object library onto the scene. Open the Attributes Inspector and ensure the position is set to X = 0, Y = 0. The camera object is the SpriteKit object that will observe the change in values and report that to the SpriteKit scene. So, there’s one final change we need to make. Select the “Scene” from the Scene Graph Inspector, then open the Attributes Inspector. Ensure that the Camera that was just added is selected under the “Scene - Camera” option.
Finally, we need to add the rotation action to the timeline. In the object library, find the “Rotate Action” and drag it to the timeline. Select the newly added rotation action and open the Attributes Inspector. Change the Degrees value to “-360” and set the Duration value to “1.” This means that SpriteKit will use this rotation action to rotate the image clockwise 360º over a duration of 1 second.
If you try to run the animation by clicking on the “Animate” button above the timeline, you’ll notice that it only animates over the 1 second duration. This is because we need to ensure it loops endlessly. To do this, click the Rotate Action in the timeline, click the “Looping” button (the clockwise icon on the action in the timeline) then select the “Endless” (∞) option.
That’s all that you need to do in order to finish up the setup of the SpriteKit scene; now let’s hook up the scene to a SpriteKit view in watchOS.
Hooking up the SpriteKit scene to a WKInterfaceController
Now that we have a looping SpriteKit scene that can be used as the progress indicator, we need to add the scene to a WKInterfaceController in the WatchKit Storyboard.
Open Interface.storyboard in your WatchKit app, and drag and drop a SpriteKit scene from the object library into the interface controller. This scene is an object based off of the class WKInterfaceSKScene.
Next, open your InterfaceController.swift file, and edit it to look like the code example below:
import WatchKit
import Foundation
import SpriteKit
class InterfaceController: WKInterfaceController {
@IBOutlet weak var spriteKitScene: WKInterfaceSKScene!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
if let scene: SKScene = SKScene(fileNamed: "ProgressIndicatorScene.sks") {
self.spriteKitScene.presentScene(scene, transition: .crossFade(withDuration: 0.1))
}
}
}
Lastly, ensure that the Interface Controller WatchKit Storyboard scene has the new spriteKitScene IBOutlet attached, then build and run the app. You’ll see that the spinner now properly spins when the watchOS app is launched and will continue doing so until the spriteKitScene is hidden from the InterfaceController view.
As you can see through this small example, things that once seemed impossible on watchOS are now possible through a different means than Core Animation. This technique can unlock a lot of potential when building watchOS apps.