Greg McKenna
Greg McKenna

Reputation: 55

How to stop button action if user moves their finger off the button in sprite kit?

 override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let positionInScene = touch!.location(in: self)
        let touchedNode = self.atPoint(positionInScene)
        if let name = touchedNode.name {
            if name == "leftbutton" {
                print("left button stopped")
                touchedNode.run(buttonStoppedPressAction)
                player?.removeAllActions()
            }
            
            if name == "rightbutton" {
                print("right button stopped")
                
                touchedNode.run(buttonStoppedPressAction)
                
                player?.removeAllActions()
            }
}
}

Here I have code that when the user lifts off their finger from the buttons it stops the action but only if they lift of their finger inside the button. So if they press it and begin to move their finger somewhere else on the screen while continuously pressing down the button will not stop executing its code. Thank you for any help.

Upvotes: 0

Views: 111

Answers (1)

Ilari Niitamo
Ilari Niitamo

Reputation: 171

Essentially you should check for touch location at touch down and compare to the location at touch up. If the touch is no longer in the area of your button, you cancel all effects.

First, though, a point. It seems like you are handling button logic in the SKScene level, which is what tutorials often tell you to do. However, this may not be the best approach. The risks here, in addition to just a cluttered mess of a SKScene, emerge from handling multiple objects and how they react to touch events, and also additional complexity from multitouch (if allowed).

Years ago when I started with SpriteKit, I felt like this was a huge pain. So I made a button class that handles all the touch logic independently (and sends signals back to the parent when something needs to happen). Benefits: No needless clutter, no trouble distinguishing between objects, the ability to determine multitouch allowances per-node.

What I do in my class to see if the touch hasn't left the button before touch up is that I store the size of the button area (as a parameter of the object) and touch position within it. Simple simple.

In fact, it has baffled me forever that Apple didn't just provide a rudimentary SKButton class by default. Anyhow, I think you might want to think about it. At least for me it saves sooo much time every day. And I've shipped multiple successful apps with the same custom button class.

EDIT: Underneath is my barebones Button class.

import SpriteKit

class Button: SKNode {
    
    private var background: SKSpriteNode?
    private var icon: SKNode?
    
    private var tapAction: () -> Void = {}
    
    override init() {
        super.init()
        
        isUserInteractionEnabled = true
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        isUserInteractionEnabled = true
    }
    
    // MARK: Switches
    
    public func switchButtonBackground(buttonBackgroundSize: CGSize, buttonBackgroundColor: SKColor) {
        background = SKSpriteNode(color: buttonBackgroundColor, size: buttonBackgroundSize)
        addChild(background!)
    }
    
    public func switchButtonIcon(_ buttonIcon: SKNode) {
        if icon != nil {
            icon = nil
        }
        
        icon = buttonIcon
        addChild(icon!)
    }
    
    public func switchButtonTapAction(_ buttonTapAction: @escaping () -> Void) {
        tapAction = buttonTapAction
    }
    
    // MARK: Touch events
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        tapAction()
    }
}

And then you create the Button object by first initiating it, assigning it a background using a size and color, then assign it an icon, assign it a function to run when tapped and finally add it as a child to the scene.

let icon = SKNode()
let size = CGSize(width: 20.0, height: 20.0)

let button = Button()
button.switchButtonBackground(buttonBackgroundSize: size, buttonBackgroundColor: .clear)
button.switchButtonIcon(icon)
button.switchButtonTapAction(buttonPressed)
addChild(button)

The background defines the touch area for the button, and you can either have a color for it or determine it as .clear. The icon is sort of supposed to hold any text or images you want on top of the button. Just package them into an SKNode and you're good to go. If you want to run a function with a parameter as the tap action, you can just make a code block.

Hope that helps! Let me if you need any further help :).

Upvotes: 2

Related Questions