9

All i'm trying to do is be able to drag and drop a sprite across the screen. I've tried the following code:

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

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for touch in (touches ) {
        let location = touch.locationInNode(self)
        if ball.containsPoint(location) {
            ball.position = location    
}
}
}

This code does work, however, when I drag the ball quite fast, I guess it detects that the "ball" no longer contains the point "location" and the ball stops, meaning I have pick the ball up again. I want the ball to be able to respond to my touches quickly, so that the ball wont stop moving. How would I do this?

Mr_Username
  • 119
  • 1
  • 7
  • 1
    Because you need to think about it logically, If you move fast, your finger no longer touches ball when it does the check. You need to use touchesBegan to start an activate state using your containPoint code, then you use touchesMoved, you just move the ball if the state is activated, you do not check the point, then on touchesEnded or touchesCanceled, you deactivate it – Knight0fDragon Aug 17 '16 at 19:26

3 Answers3

17

This is the correct way to do it in Sprite Kit. Like I said in my comment, you need to assign the moving node to an activate state, in this case I use a variable called movableNode to act is my activate state. When you touch the ball, it becomes activated by assigning it to movableNode. Then as you drag your finger, movableNode will go with the drag, Then once you release, you enter a deactivate state by setting movableNode to nil. Note that this code will only work on single touch applications, if you want to handle multitouch, then you need to track which touch is doing the dragging.

var movableNode : SKNode?

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
        let location = touch.locationInNode(self)
        if ball.containsPoint(location) {
            movableNode = ball
            movableNode!.position = location    
        }
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first where movableNode != nil {
        movableNode!.position = touch.locationInNode(self)
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first where movableNode != nil {
        movableNode!.position = touch.locationInNode(self)
        movableNode = nil
    }
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
        movableNode = nil
    }
}
Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
9

KnightOFDragon solution works just fine. I just added few lines if you don't want to move sprite centre position to position where your finger touched the screen and you would like to move sprite from its centre original position.

var movableNode : SKNode?
var ballStartX: CGFloat = 0.0
var ballStartY: CGFloat = 0.0

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first {
        let location = touch.location(in: self)
        if (map?.contains(location))! {
            movableNode = ball
            ballStartX =  (movableNode?.position.x)! - location.x // Location X of your sprite when touch started
            ballStartY =  (movableNode?.position.y)! - location.y // Location Y of your sprite when touch started
        }
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let touch = touches.first, movableNode != nil {
        let location = touch.location(in: self)
        movableNode!.position = CGPoint(x: location.x+ballStartX, y: location.y+ballStartY) // Move node around with distance to your finger
    }
}

override  func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    if let _ = touches.first, movableNode != nil {
        movableNode = nil
    }
}
 override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
    movableNode = nil
}
MOzeb
  • 423
  • 1
  • 6
  • 14
  • Brilliant. Thankyou. – Steve Dec 23 '16 at 11:05
  • So this would basically only make my node movable only if you drag it right? Cause when I tap certain parts of the display, the node moves there. I don't want the node to move unless I'm dragging somewhere on the screen for it. – Dewan Dec 13 '17 at 18:08
-2

I have an implementation where I've subclassed a UIImageView and called it a "DraggableImage"

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        originalPosition = self.center
    }

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            let position = touch.location(in: self.superview)
            self.center = CGPoint(x: position.x, y: position.y)
        }
    }

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

        self.center = originalPosition
    }
Ryan Collins
  • 679
  • 1
  • 6
  • 13
  • 2
    How is this even an answer, SKNodes and UIViews are 2 different things, and if you move fast, you should still have the problem of this Draggable Image touchesMoved not being fired because you went beyond the frame – Knight0fDragon Aug 18 '16 at 13:57