Search code examples
sprite-kitswift2uigesturerecognizertouchesbegan

Mix up between UIGestureRecogniser and touchesBegan


I'm using both UISwipeGestureRecognizer and touchesBegan in a SpriteKit scene. UISwipeGestureRecogniser is initiated in didMoveToView():

let swipeRight:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedRight:"))
swipeRight.direction = .Right
view.addGestureRecognizer(swipeRight)

let swipeLeft:UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("swipedLeft:"))
swipeLeft.direction = .Left
view.addGestureRecognizer(swipeLeft)

touchesBegan() is used to transition to another scene when nodes are touched. The nodes are identified by their names, in some cases by the prefix to their name.

if (node.name?.hasPrefix("enemy") != nil) {
    let transition = SKTransition.crossFadeWithDuration(StandardSettings.SceneManager.transitionDuration)
    let enemyScene = EnemyScene(size: self.frame.size)
                    enemyScene.scaleMode = .AspectFill
    view?.presentScene(enemyScene, transition: transition)
}

Finally, there are functions for swipedRight() and swipedLeft(). These move an SKShapeNode from left to right (or the reverse).

func swipedRight(sender:UISwipeGestureRecognizer) {            
shapeNode.runAction(SKAction.moveByX(UIScreen.mainScreen().bounds.size.width * 0.5, y: 0, duration: 5.0))
}

This all works well, except when a node, identified by it's prefix is touched during a swipe movement. When there's no contact with the node, the shapeNode moves across the screen as expected. When there is contact, the transition code to a new scene is also being called. It seems this is only happening when the enemy node is being identified by its name's prefix. Nodes identified by a full name comparison with a string don;t seem to have this issue. Is there a way to disable touchesBegan() while the code executes a swipe-triggered method?


Solution

  • One way to determine if a touch event is a swipe gesture or a simple touch is to test the distance between the starting and ending touch locations. The following is an example of how to do this.

    First, define a variable that stores the initial touch position

    var touchStart:CGPoint?
    

    and a constant to test the distance between the touch locations. Adjust this value as needed.

    let minSwipeDistance:CGFloat = 22
    

    In the touchesBegan method, store the initial touch location

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let touch = touches.first {
            touchStart = touch.locationInNode(self)
        }
    }
    

    and in touchesEnded, compare the distance between the current and initial touch locations. If this distance is relatively small, the event is not a swipe.

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        if let touch = touches.first, start = touchStart {
            let location = touch.locationInNode(self)
            // Compute the distance between the two touches
            let dx = location.x - start.x
            let dy = location.y - start.y
            let distance = sqrt(dx*dx + dy*dy)
            if distance < minSwipeDistance {
                let node = nodeAtPoint(location)
                if let name = node.name where name.hasPrefix("enemy") {
                    print ("transition to new scene")
                }
            }
        }
        // Reset the initial location
        touchStart = nil
    }