nathandemick.com

Buttons in Sprite Kit using Swift

For all the helpful things that Apple added to Sprite Kit, one of the most glaring omissions is that most basic of user interface elements, the button. Not really sure why that is, but fortunately it’s pretty easy to create something that “just works.” The gist of the solution is to create a subclass of the SKNode class. Since each node in Sprite Kit can contain any number of other nodes, we just have to create a button container which holds two sprites: one for the default button state and one for the “active” button state.


import SpriteKit

class GGButton: SKNode {
    var defaultButton: SKSpriteNode
    var activeButton: SKSpriteNode
    var action: () -> Void
    
    init(defaultButtonImage: String, activeButtonImage: String, buttonAction: () -> Void) {
        defaultButton = SKSpriteNode(imageNamed: defaultButtonImage)
        activeButton = SKSpriteNode(imageNamed: activeButtonImage)
        activeButton.hidden = true
        action = buttonAction
        
        super.init()

        userInteractionEnabled = true
        addChild(defaultButton)
        addChild(activeButton)
    }

    /**
        Required so XCode doesn't throw warnings
    */
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  

Here we create that subclass of SKNode and give it three properties: the default button image, the active button image, and the action that the button should take if pressed. Passing in two strings to the constructor handles initializing the button images, and we also store a reference to the button action function. Note of course that we hide the “active” button by default, waiting for user interaction to reveal it.

After the call to super.init(), the button container has access to methods inherited from SKNode. We then set the userInteractionEnabled property to true, which lets this node respond to input, and also add both buttons as children so they’ll be drawn to the screen.

Now let’s deal with user input. We want to handle three cases: user touches the button, user moves their finger around, user lifts their finger off the button. Fortunately the user interaction methods provided by SKNode give us exactly that, if you add the following three methods to the button class definition.

  
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    activeButton.hidden = false
    defaultButton.hidden = true
}
  

This first method is pretty obvious: if a user starts touching the button, show the “active” state.


override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
    var touch: UITouch = touches.allObjects[0] as UITouch
    var location: CGPoint = touch.locationInNode(self)

    if defaultButton.containsPoint(location) {
        activeButton.hidden = false
        defaultButton.hidden = true
    } else {
        activeButton.hidden = true
        defaultButton.hidden = false
    }
}

This next method is a bit more complex. We have to determine if a user moved their finger on or off the button, so as to show the appropriate highlight state. Fortunately, SKNode-derived objects have a method containsPoint(), which lets us do some easy collision detection.


override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
    var touch: UITouch = touches.allObjects[0] as UITouch
    var location: CGPoint = touch.locationInNode(self)

    if defaultButton.containsPoint(location) {
        action()
    }
    
    activeButton.hidden = true
    defaultButton.hidden = false
}

Finally, we re-use the containsPoint() method described earlier in order to determine if the button was actually tapped. If it was, we call the “action” function that was provided to the constructor. Then we set the highlighted button state back to hidden, ready to be shown again on the next tap. Put it all together, and here’s how you’d use the button in a game:


var button: GGButton = GGButton(defaultButtonImage: "button", activeButtonImage: "button_active", buttonAction: goToGameScene)
button.position = CGPointMake(self.frame.width / 2, self.frame.height / 2)
addChild(button)

I hope this was helpful. Feel free to ask questions in the comments section, and I’ll do my best to answer.

· 6 comments


Comments

falconlover wrote on :

Hello, I am having an issue with goToGameScene and what it needs to be. I have tried a literal like UIButton uses (i.e. "btnClick:") but get an error. I guess I am just not understanding the syntax of what:"() -> Void" is. Can you clarify? Thank you.

EW Parris wrote on :

This is a great basic button class. This helped me to create my own family of button classes in swift. Thanks for the starting point!

TPot wrote on :

Thanks. Just tried it and it works nicely - now expanding on it. @falconlover: ()->void is saying the argument is a function with no arguments and returns nothing. ie. you'd define "goToGameScene" as a function somewhere in your class.

André wrote on :

Thanks a lot, very good tutorial!

Jon wrote on :

@TPot Please explain why this works. I too do not understand. I just want my button to briefly change images following the tap. I don't know how to implement the goToGameScene. I don't know where it must go or what code must go underneath it

Moon wrote on :

When i create a function in the class as TPot suggested, i cannot pass this through as a parameter