6

I'm attempting to create a subclass of SKSpriteNode which can detect user interaction (tap, double tap and hold), then defer to a delegate. This seems to me like a relatively common need, but:

  1. The code is incapable of detecting a double-tap without also triggering a single-tap.
  2. I haven't found a way to detect hold/long-press actions at all.

Am I going about this all wrong? I can't help feeling that SpriteKit should have something standard to handle something so basic. I know there's UIGestureRecognizer, but it doesn't seem very compatible with specific SKNodes rather than UIViews.

Here's what I have currently. Based on examples from Apple, I have all of the non-device-specific code in the main class file:

import SpriteKit

enum ActionType: Int {
    case Single
    case Double
    case Hold
}

protocol EnvironmentElementDelegate {
    func handleActionOnElement(element: EnvironmentElement, actionType: ActionType)
}

class EnvironmentElement: SKSpriteNode {

    let delegate: EnvironmentElementDelegate!

    init(imageNamed: String, elementNamed: String, delegate: EnvironmentElementDelegate) {
        self.delegate = delegate
        let texture = SKTexture(imageNamed: imageNamed)
        super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
        self.name = elementNamed
        userInteractionEnabled = true
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

...then all of the iOS-specific code in a separate extension file:

import SpriteKit

extension EnvironmentElement {

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        for touch: AnyObject in touches {
            if (touch.tapCount >= 2) {
                NSObject.cancelPreviousPerformRequestsWithTarget(self)
            }
        }
    }

    override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
        for touch: AnyObject in touches {
            if (touch.tapCount == 1) {
                delegate.handleActionOnElement(self, actionType: ActionType.Single)
                // Unable to find Swift equivalent to this: [self performSelector:@selector(onFlip) withObject:nil afterDelay:0.3];
            } else {
                delegate.handleActionOnElement(self, actionType: ActionType.Double)
            }
        }
    }
}
TheNeil
  • 3,321
  • 2
  • 27
  • 52
  • Usually when I need an Object that have to detect the user tap I use the SKNode, it works better, I had a lot of problem using SKSpriteNode for this. And for the iOS specific code, I add it to the class itself, also this works better. – Totka Sep 29 '14 at 15:48
  • Thanks for the reply Totka, but would you not face the same issues in an SKNode too? How do you detect double taps and long holds rather than single taps? My concern is that perhaps my code is far from whatever the best practice may be for identifying such fundamental user inputs. – TheNeil Sep 29 '14 at 15:50
  • 1
    I suggest you use the built-in UIGestureRecognizers because they support long presses and double taps (with single-tap rejection). – 0x141E Oct 01 '14 at 08:05
  • Thanks. Though as I mentioned above, UIGestureRecognizers don't seem to be compatible with SpriteKit nodes: there's no function to add them. This is very much why I'm not convinced of my design. Been reading more about MVC and maybe I should be working out how to add the recogniser at the view level and then detect which node was tapped from there? – TheNeil Oct 01 '14 at 13:00
  • Hi. Thank you for adding [tag:ios] on popular uiscrollview questions, but as it's a too minor edit, you'll likely face rejections soon by other reviewers. Better also review the whole post for improvements and/or wait for 2000 reputations when mass editing. – Cœur May 27 '19 at 17:37
  • Aside from using touch.tapCount, you could use a UIGestureRecognizer per previous suggestion. You do not have a GUI for this, you must do it in code. Here is a good example that shows translating the touch point, your game would then find the node touched. http://spritekitlessons.com/gesture-recognizer-examples-with-swift-and-sprite-kit/ – John Nov 11 '20 at 18:19

2 Answers2

5

You can simply use touch.tapCount inside touchesBegan:

ddb
  • 2,423
  • 7
  • 28
  • 38
topclaudy
  • 779
  • 8
  • 8
  • It works well. Just be aware that this does not prevent the first touch events firing, e.g. tapCount==1 touch still fire on `touchesBegan` and `touchesEnded`. So you still need to work out, possibly with Timer, to delay to recognize if it is a true single tap or double one. – Juguang Jul 03 '20 at 06:28
3

I never had to detect a double tap, but if I have I will do it like this:

1 - add a local var named alreadyTouch and set it to false when the class is initialized

2 - inside touchesBegin if alreadyTouch is false set it to true else do what you have to

3 - setup a local Timer of 0.2 seconds and add also this to the if else to detect if the touch is rapid enough or not. so start this timer where you set alreadyTouch = true.

Hope it helps

ddb
  • 2,423
  • 7
  • 28
  • 38
Totka
  • 627
  • 6
  • 24
  • Hi Totka, please can you put some example lines of code, like how would you run the local Timer? – Efren Oct 20 '17 at 06:49
  • hello, I'm sorry but I've lost that project, or better, the hd with that project – Totka Oct 21 '17 at 15:03