5 approach to implement SpriteKit drag

Implementing SpriteKit drag could be tricky. On one hand you can find a running animation (I’m not sure that it is synced with UIKit touch events), on the other hand you (may) have a physics simulation to fit in.

Simple SpriteKit drag

If you’re not using physics, and do not consider multiple touches, the you should not read the rest below actually. Simply get a touch location from touchesBegan: and touchesMoved: event callbacks, then adjust the location to a node.position of your choice. Only thing here is to convert coordinates using the handy UITouch extension locationInNode:.

For one of my prototypes, this approach was fairly enough, even with setting the node centered to the touch (was inspecting physics body collisions only).

@interface EPPZScene ()
@property (nonatomic, weak) SKNode *draggedNode;
@end
-(void)touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{ self.draggedNode = [self nodeAtPoint:[[touches anyObject] locationInNode:self]]; }

-(void)touchesMoved:(NSSet*) touches withEvent:(UIEvent*) event
{ self.draggedNode.position = [[touches anyObject] locationInNode:self]; }

-(void)touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{ self.draggedNode = nil; }

SpriteKit drag with physics

You can embed this method into a physics world (with gravity and other forces), but that case you have to suspend physics forces while a node is dragged. Actually turning off node dynamics and setting velocity to CGPointZero for the sake of safety seems fine, maybe you should turn off affectedByGravity as well and for some cases that could be enough at all. These changes will still preserve collisions while simulating physics.

-(void)setDraggedNode:(SKNode*) draggedNode
{
    // Previous.
    _draggedNode.physicsBody.affectedByGravity = YES;
    
        _draggedNode = draggedNode; // Set
    
    // New.
    draggedNode.physicsBody.affectedByGravity = NO;
}

Subclass SKNode adding draggable features

After a certain point of complexity, it is a really good trade to subclass SKSpriteNode, or SKShapeNode to encapsulate dragging features. For example, even with these simple dragging cases we could incorporate a property touchOffset that remembers the finger offset from node.position at the time of touch has began.

@interface EPPZDraggableShapeNode : SKShapeNode
@property (nonatomic) CGPoint touchOffset;
@end

@implementation EPPZDraggableShapeNode
@end

Having this shape nodes like this, we can modify the previous code utilizing this awesome feature. We have to care about the actual node class that nodeAtPoint: returns this time, as only EPPZDraggableShapeNode has the touchOffset property we want.

-(void)touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
    // Get node if any.
    CGPoint touchLocation = [[touches anyObject] locationInNode:self];
    EPPZDraggableShapeNode *touchedNode = (EPPZDraggableShapeNode*)[self nodeAtPoint:touchLocation];
    if ([touchedNode isKindOfClass:[EPPZDraggableShapeNode class]] == NO) return; // Checks
    
    // Track and save offset (with the new SKNode feature).
    self.draggedNode = touchedNode;
    self.draggedNode.touchOffset =
    subtractVectorPoints(touchLocation, self.draggedNode.position);
}

-(void)touchesMoved:(NSSet*) touches withEvent:(UIEvent*) event
{
    // Align with offset (if any)
    CGPoint touchLocation = [[touches anyObject] locationInNode:self];
    self.draggedNode.position =
    subtractVectorPoints(touchLocation, self.draggedNode.touchOffset);
}

-(void)touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
    self.draggedNode = nil;
}

Don’t get confused about those math helpers like subtractVectorPoints(), just some coordinate juggling I created before. Part of EPPZGeometry.h if you’re interested in more detail.

See EPPZGeometry.h at

Multi-touch SpriteKit drag

Once you need multiple touches, the I found that the best way is to simply bind every touch to a node. Thats all, no touch sorting or any other overwhelming stuff. Just bind the touch to a node that has no touches yet. If you’re not quiet sure about it, you can find touches in the first parameter of touchesBegan:.

Having this you can easily update node’s position in every frame in SKScene‘s update: method without worrying about when those touches get updated. Actually you don’t even have to implement touchesMoved: method at all. Only bind touches when a touch is began, then unbound the touch when it is released. You’d be surprised how simple is to going this way.

So having already subclassed SKShapeNode before, we have a good place where we can encapsulate dragging features, safely away from the controller code, which I really like. Now gently move down the dragging features to EPPZDraggableShapeNode.

@interface EPPZDraggableShapeNode : SKShapeNode
@property (nonatomic) CGPoint touchOffset;
@property (nonatomic, weak) UITouch *touch;
@property (nonatomic, readonly) BOOL isDragged;
-(void)bindTouch:(UITouch*) touch;
-(void)drag;
-(void)unbindTouch:(UITouch*) touch;
@end
@implementation EPPZDraggableShapeNode

-(BOOL)isDragged
{ return (self.touch != nil); }

-(void)bindTouch:(UITouch*) touch
{
    self.touch = touch; // Reference
    
    // Physics, and coordinate works moved here.
    CGPoint touchLocation = [self.touch locationInNode:self.scene];
    self.touchOffset = subtractVectorPoints(touchLocation, self.position);
    self.physicsBody.affectedByGravity = NO;
}

-(void)drag
{
    // If any touch bound.
    if (self.isDragged == NO) return;

    // Coordinate works moved here.
    CGPoint touchLocation = [self.touch locationInNode:self.scene];
    self.position = subtractVectorPoints(touchLocation, self.touchOffset);
}

-(void)unbindTouch:(UITouch*) touch
{
    // Unbind only if bound.
    if (self.touch != touch) return;
    
    // Physics work moved here.
    self.touch = nil;
    self.physicsBody.affectedByGravity = YES;
}

@end

Binding touch is actually just hold a weak reference for it in touch property. When you bind a certain touch, there is a good chance to save the touchOffset and suspend physics, as the node is gonna be dragged from that time. You can resume physics when the touch is gonna be unbound. Now using them in the scene – for a single touch yet – goes like:

-(void)touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
    // Get node if any.
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInNode:self];
    EPPZDraggableShapeNode *touchedNode = (EPPZDraggableShapeNode*)[self nodeAtPoint:touchLocation];
    if ([touchedNode isKindOfClass:[EPPZDraggableShapeNode class]] == NO) return; // Checks
    
    // Bind.
    [touchedNode bindTouch:touch];
    self.draggedNode = touchedNode;
}

-(void)touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
    UITouch *touch = [touches anyObject];
    [self.draggedNode unbindTouch:touch];
}

-(void)update:(NSTimeInterval) currentTime
{
    [self.draggedNode drag];
}

Thing are just the same for multiple touches, with some enumerations. So while leaving the shape node subclass unchanged – beside some wording – you can enumerate touches and scene children to manage bindings. For production code I extracted binding management as well along some considerations, but for the sake of current article, this implementation fits well.

-(void)touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event
{
    for (UITouch *eachTouch in touches) // Enumerate touches
    {
        CGPoint eachTouchLocation = [eachTouch locationInNode:self];
        NSArray *nodes = [self nodesAtPoint:eachTouchLocation];
        for (EPPZDraggableShapeNode *eachTouchedNode in nodes) // Enumerate nodes beneath
        {
            if ([eachTouchedNode isKindOfClass:[EPPZDraggableShapeNode class]] == NO) continue; // Check class
            if (eachTouchedNode.isDragged) continue; // Skip if already bound
            [eachTouchedNode bindTouch:eachTouch];
        }
    }
}

-(void)touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event
{
    for (UITouch *eachTouch in touches) // Enumerate touches
    {
        for (EPPZDraggableShapeNode *eachTouchedNode in self.children) // Enumerate nodes
        {
            if ([eachTouchedNode isKindOfClass:[EPPZDraggableShapeNode class]] == NO) continue; // Check class
            [eachTouchedNode unbindTouchIfNeeded:eachTouch];
        }
    }
}

-(void)update:(NSTimeInterval) currentTime
{
    for (EPPZDraggableShapeNode *eachTouchedNode in self.children) // Enumerate nodes
    {
        if ([eachTouchedNode isKindOfClass:[EPPZDraggableShapeNode class]] == NO) continue; // Checks
        [eachTouchedNode dragIfNeeded];
    }
}

To avoid redundant checks, best solution would be to swizzle a node property for every UITouch, but that is beyond the scope for now. This implementation can drag every node in you scene with theoretically unlimited number of touches. Already tested with 10 simultaneous touches, everything just went fine.

The next step could be some refined hit testing for every draggable node wrapped up to a method like -(BOOL)isPointHit:(CGPoint) point, or even straight to touches like -(BOOL)isTouchHit:(UITouch*) touch. It is really straightforward being an SKShapeNode subclass, as this kind of node has a path property you can test CGPoint containment against (after applied the momentary transformations). Or for circle based bodies you can simply measure a distance and test against radius.

I made a testbed project for the methods discussed above, you can find it at GitHub. It includes everything up to this point (beside hit testing), so you can easily get a kick of what I was talking about with these bindings.

Try blog.SpriteKit_drag at

Life for SKNodes is more complicated than just adjusting their position. The considerations those taking into account while being in a more complicated physics simulation are simply too weighted to include them in this article at code level. Let’s have some word at some theoretic level below.

Physics-proof multi-touch SpriteKit drag

Like said before, adjusting the position directly can not simulate real world physics, as you can still drag a ball trough a solid wall. We need a vector, a force to move with instead of adjusting position. So instead of adjusting the node.position directly, we can safely adjust the node.physicsBody.velocity property.

According to my inspectations the velocity unit is measured in pixel/second that makes things much straightforward. So we need a vector that points from the current node.position to the desired position, than simply scale (multiply) it by elapsed time. You can pick up an elapsed time in the update: method using the currentTime parameter with some counting.

Dragging still can be a node level feature, but in this case you have to create an interface something like -(void)dragForDuration:(NSTimeInterval) frameDuration or something similar.

Having this collisions and joints will be evaluated gracefully during the simulation resulting in a much realistic drag experience. You cannot drag a node over a wall anymore.

Avoid position flickering during a SpriteKit drag

The only thing left is that adjusting too big velocity can lead to unwanted flickering frames. Simply physics engine cannot handle those extremes. On one hand I could lower flickering by turning off any kind of bounciness (restitution) on nodes and even on the world.physicsBody as well (that can stop wall bouncing surprisingly). But overtense some smring joint will still result in flickering positions.

The solution here is to introduce an itermetiate node that gets joined to the node every time you want to drag it (also need to be removed when the particular touch has ended). With a SKPhysicsJointSpring you can adjust the node’s responsiveness by experimenting with the spring joints properties of your taste.

After this on updates you’ll adjust this gizmo node’s position only (either directly or trough velocity adjustment) instead of set the draggee node position. So far the most lovable results, I can use this in production.

What most makes this method is the best is that you can attach the spring joint to the node at the point user touched it, so having “well-tempered” physics parameters, you can move and even rotate a node with a single touch.

DISCLAIMER. THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.) IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.).

  • Kenny Ackerson

    Can you please provide a code sample for “Physics-proof multi-touch SpriteKit drag” I can not figure this out ATM, thanks

    • eppz

      Sorry to say, but I’m not intended to open the source of my upcoming title. Once it gets released, I probably will extract many code and publish them as part of eppz!kit. Multi-touch dragging will be a part of that for sure.

  • Kal

    Hi,
    Very nice writeup. Could you please explain what do you mean by Bind / Unbind touches (without updating Sprite position in touchesMoved method) ? I couldn’t find any such term when I googled it.

    Thanks,
    Kal

    • eppz

      Yep, gonna edit the article right away, I’m apparently managing the blog anyway.

  • Ofer

    when saying “Only bind touches…” on the section Multi-touch SpriteKit drag, can you elaborate?
    do you mean keeping UITouch object pointer and re-use it till touch ends?

    • eppz

      Yep, see codes.

      Don’t think of it as reuse, UITouch objects are living until the touch is present (and moving), also they get updated. Every locationInSomething will give you the momentary locations.

  • Akshay

    Hi,

    Thanks for this example.
    I am having an error i.e. Property ‘touchOffset’ not found on object of type ‘SKNode *’. in my touchBegun and touchMoves method in MyScene.m file.

    so, if you can help me out than it would be very helpful.

    Thanks in advance.