1

I'm in the process of learning swift (and spritekit) and trying to make a simple game.

In my game, the hero should jump or duck...

The hero needs to jump when the screen is tapped,or duck if the screen is tap+held (long gesture)

So basic pseudo code:

if tapped
    heroJump()
else if tappedAndHeld
    heroDuck()

I have a func which is seen in almost all tutorials, which handles the touch event:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

for touch in touches {
let location = touch.locationInNode(self) //need location of tap for something

    switch gameState {
        case .Play:
            //do in game stuff

            break
        case .GameOver:
            //opps game ended

            }
            break
    } 
 }
   super.touchesBegan(touches, withEvent: event)
}

Is there a way, to include in this touch event, to decide if it was tapped or held? I can't seem to get my head around the fact, the program will always recognise a tap before a long gesture?!?

Anyway, in an attempt to solve my problem, I found THIS question, which introduced to me recognisers, which I tried to implement:

override func didMoveToView(view: SKView) {
    // other stuff

    //add long press gesture, set to start after 0.2 seconds
    let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
    longPressRecognizer.minimumPressDuration = 0.2
    self.view!.addGestureRecognizer(longPressRecognizer)

    //add tap gesture
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tapped:")
    self.view!.addGestureRecognizer(tapGestureRecognizer)
}

And these are the functions the gestures call:

func longPressed(sender: UILongPressGestureRecognizer)
{
    if (sender.state == UIGestureRecognizerState.Ended) {
        print("no longer pressing...")
    } else if (sender.state == UIGestureRecognizerState.Began) {
        print("started pressing")
        // heroDuck()
    }
}

func tapped(sender: UITapGestureRecognizer)
{
    print("tapped")
    // heroJump()
}

How can I combine these two things? Can I add a way to determine whether it was tapped or hold in my touchBegins event, or can I scrap that method and use only the two functions above?

One of many problems being getting the location if using the latter?

Or maybe I'm looking at this completely wrong, and there's a simple and/or built in way in swift/spritekit?

Thanks.

Community
  • 1
  • 1
Reanimation
  • 3,151
  • 10
  • 50
  • 88

2 Answers2

6

You only need the UITapGestureRecognizer and the UILongPressRecognizer. You do not need to do anything with touchesBegan and touchesEnded, because the gesture recognizer analyses the touches itself.

To get the location of the touch you can call locationInView on the gesture recognizer to get the location of the gesture or locationOfTouch if you are working with multitouch gestures and need the location of each touch. Pass nil as parameter when you want the coordinates in the window’s base coordinate system.

Here is a working example:

func setupRecognizers() {
     let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
     let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("handleLongPress:"))
     view.addGestureRecognizer(tapRecognizer)
     view.addGestureRecognizer(longTapRecognizer)
}

func handleLongPress(recognizer: UIGestureRecognizer) {
    if recognizer.state == .Began {
        print("Duck! (location: \(recognizer.locationInView(nil))")
    }
}

func handleTap(recognizer: UIGestureRecognizer) {
    print("Jump! (location: \(recognizer.locationInView(nil))")
}

If a long press is recognized handleTap: tap is not called. Only if the user lifts his finger fast enough handleTap: will be called. Otherwise handleLongPress will be called. handleLongPress will only be called after the long press duration has passed. Then handleLongPress will be called twice: When the duration has passed ("Began") and after the user has lifted his finger ("Ended").

joern
  • 27,354
  • 7
  • 90
  • 105
  • Nice answer, I will look into this now. How do you cancel a LongPress? for example, if the gesture hasn't finished (or still holding), how can you cancel the gesture even if the user is still holding? If that makes sense... Also, is it possible to limit the LongPress duration, for example, no matter hold long the user presses the screen, the LongPress only lasts say 3 seconds? Thanks for your time. – Reanimation Dec 16 '15 at 15:52
  • I think I found how to cancel it, but it seems slow: `recognizer.cancelsTouchesInView = true` – Reanimation Dec 16 '15 at 15:56
  • No, that property determines whether the gesture recognizer will pass the touches to the view in case he recognizes a gesture. By default a gesture recognizer cancels touches when he recognizes a gesture. By setting this property to `false` the view will get the touches even if a gesture is recognized. – joern Dec 16 '15 at 15:59
  • I am not sure why you want to cancel anything. The way the example works is that if the user touches the view for 0.5s (default value for long press) the "Duck" gets triggered. It does not matter how long he touches the view after that, because the "Ended" state of the recognizer is ignored. – joern Dec 16 '15 at 16:00
  • Oh, my mistake. My game states where getting out of order since introducing `longPress` so it was crashing... I figured I needed to cancel the touch, but it was in fact because my `gameOver()` method wasn't being called if I was ducking. Rookie error no doubt. – Reanimation Dec 16 '15 at 16:08
0

you do the same thing you are doing for longpress, wait till the .Ended event

func tapped(sender: UITapGestureRecognizer)
{

    if sender.state == .Ended {
      print("tapped")
    }
}

A tap event will always happen, this can't be prevented because lets face it, you need to touch the screen. What should be happening though is when you enter the long press event, the tap event should go into a Cancel state instead of an Ended state

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • 1
    You are right that the touch will always happen, but the gesture recognizers are "clever" enough to only call `tapped` OR `longPressed`. When the touch was long enough only `longPressed` will be called. If the user lifts his finger before it is considered a long press, only `tapped` is called. Never both. – joern Dec 16 '15 at 15:36
  • You are right, the tap gesture .Began state no longer gets called. looks like I can clean up some of my old code – Knight0fDragon Dec 16 '15 at 15:48