Controls

While SpriteKit provides some basic things (sprites, labels, etc.), it doesn’t provide true SpriteKit controls like pushbuttons, radio buttons, checkboxes, etc. If you search the internet you can find various solutions that folks have offered. I found many didn’t quite work the way I wanted them to or were a bit grandiose in their implementation.

I found my favored approach was to subClass SKSpriteNode and add to it any required children to support the desired control.  I needed a toggle switch control for my settings dialog.  A toggle switch would contain the image for selected or unselected state, the text for the description, and a touch panel over the top of it.  Add to that a couple of sounds for selecting and deselecting the toggle switch.

I chose the follow parameters for the initialization call:

    convenience init(name: String, text: String, isSelected: Bool, zPosition: CGFloat, fontName: String, fontSize: CGFloat, fontColor: UIColor, backColor: UIColor = UIColor.clear) {

 This gives me a name for the control, the text to be displayed, the vertical position of the control, the font to be used, the size of the font, the color of the font, and the color the base sprite node will have.

You may have noticed the size of the control is not specified.  This is determined by the size of the image and the font size.  First thing is to create a temp SKLabelNode and assign all the properties to it which allows us to get the size from the frame.

        let tempLabel = SKLabelNode.init(text: text)
        tempLabel.fontName = fontName
        tempLabel.fontSize = fontSize
        tempLabel.horizontalAlignmentMode = .left
        tempLabel.verticalAlignmentMode = .baseline

Now we have a frame to look at.  I added  what I’m calling a gutter, the space above and below the text.  This only becomes relevant when the image height is smaller than the text height.
        let tempLabelHeight: CGFloat = fontSize + 5     // (gutterSize of 3 * 2) – 1

Now we can create the base sprite.  The sprite width will be the width of the image, plus a small separator space, plus the label frame width.  The height will be the larger of the two heights;  tempLabelHeight and image height.

For my purposes, the image will always be on the left side.  A sprite is created to hold the image and added as a child of the base sprite.  Next a label is created for the text.  It is placed to the right of the image plus a small separator space.

The last thing I add is a touch panel.  This is a clear sprite the size of the base sprite that sits over the top.  I found this the easiest way to manage touches.

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if (!isEnabled) {
            return
        }
        
        guard let touch = touches.first else {
            return
        }
        
        let touchLocation = touch.location(in: self)
        if self.touchCover.contains(touchLocation) {
            self.isSelected = !self.isSelected
            
            if delegate != nil {
                delegate.selectedToggleSwitchState(toggleSwitch: self)
            }
        }
    }

Here is an example of what my toggle switch looks like:

toggle switch example

And here it is in the full Settings dialog:

settings panel example