61

I am trying to detect if my sprite node has been touched and I have no idea where to start.

let Pineapple = SKSpriteNode(imageNamed: "Pineappleimg")
Pineapple.userInteractionEnabled = true
Pineapple.position = CGPoint(x: CGRectGetMidX(self.frame) - 200, y: CGRectGetMidY(self.frame));
self.addChild(Pineapple)
Confused
  • 6,048
  • 6
  • 34
  • 75
James Brennan
  • 635
  • 1
  • 6
  • 7
  • This question can be used as an example: http://stackoverflow.com/questions/21840042/cant-tap-skspritenode-no-touch-detected-ios – Emil Jan 13 '15 at 12:45

11 Answers11

79

First set the name property of the SKSpriteNode to a string.

pineapple.name = "pineapple"
pineapple.userInteractionEnabled = false

then in touchesBegan function in the Scene

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    let touch:UITouch = touches.anyObject()! as UITouch
    let positionInScene = touch.locationInNode(self)
    let touchedNode = self.nodeAtPoint(positionInScene)

    if let name = touchedNode.name
    {
        if name == "pineapple"
        {
            print("Touched")
        }
    }

}

This is one way to do it.
You can also subclass SKSpriteNode and override the touchesBegan inside it.

class TouchableSpriteNode : SKSpriteNode
{
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        print("touched")
    }
}

Then do

let pineapple = TouchableSpriteNode(imageNamed: "Pineappleimg")
pineapple.userInteractionEnabled = true
pineapple.position = CGPoint(x: CGRectGetMidX(self.frame) - 200, y: CGRectGetMidY(self.frame));
self.addChild(pineapple)
loicbr
  • 5
  • 2
rakeshbs
  • 24,392
  • 7
  • 73
  • 63
  • Fails to compile in swift 3. – Parth Sane Mar 01 '17 at 13:16
  • 4
    **Swift4.1**. override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touch:UITouch = touches.first! as UITouch let positionInScene = touch.location(in: self) let touchedNode = self.atPoint(positionInScene) if let name = touchedNode.name { if name == "pineapple" { print("Touched") } } } – uplearned.com Apr 23 '18 at 03:15
21

If you are looking only for a few nodes that can be touched (e.g., the "Continue" or "Exit" labels in a game UI), this might be an alternative but very simple solution:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    if myNode.containsPoint(touch.locationInNode(self)) {
        print("touched")
    }
}
Florian Blum
  • 648
  • 6
  • 16
  • How does this differentiate between Continue and Exit labels? – Confused Sep 27 '16 at 19:49
  • might be... maybe... that you're suggesting this code be in both labels, Continue and Exit, and that one of them will be called when touchesBegun information is sent to it... because it's registered for the touch information by virtue of over-riding touchesBegun. Is that right, or am I... – Confused Sep 27 '16 at 19:54
  • Of course this will not work properly for overlapping or intersecting labels but touchesBegan() is meant to be overridden for the whole scene and is not a method of a label. – Florian Blum Oct 01 '16 at 22:09
  • So touchesBegun() can only exist in scene classes? – Confused Oct 01 '16 at 22:21
  • In fact it's inherited by UIResponder: https://developer.apple.com/reference/uikit/uiresponder/1621142-touchesbegan ... which is the superclass of UIView. SKScene contains an SKView which inherits from UIView :) – Florian Blum Oct 14 '16 at 10:47
  • So all SKSpriteNodes are UIResponders? – Confused Oct 14 '16 at 10:56
  • I've just learnt that ALL SKNodes have UIResponder as conformed protocol. Which I think means just about any node can ask to be called when its touched. Am I understanding this right? – Confused Oct 14 '16 at 14:06
  • Yes - if they contain something visible. An SKNode could contain several e.g. SKSpriteNodes and when one of these SKSpriteNodes is touched their own touchesBegan()-Method is called and not the one of the parent SKNode. – Florian Blum Oct 19 '16 at 20:08
12

Update for Swift Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1) and XCode Version 8.2.1 (8C1002):

Value of type 'Set' has no member 'anyObject'

'locationInNode' has been renamed to 'location(in:)'

'nodeAtPoint' has been renamed to 'atPoint(_:)'

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for touch in touches {
        let location = touch.location(in: self)
        let node : SKNode = self.atPoint(location)
        if node.name == "myNodeName" {
            print("Hello")
        }
    }
}
Oleksandr Lykhonosov
  • 1,138
  • 12
  • 25
7

This will detect touches in Xcode 9.2 Swift 4.0

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
    let touch:UITouch = touches.first!
    let positionInScene = touch.location(in: self)
    let touchedNode = self.atPoint(positionInScene)

    if let name = touchedNode.name
    {
        if name == "playLbl"
        {
            print("playLbl Touched")
        }
    }

}
uplearned.com
  • 3,393
  • 5
  • 44
  • 59
  • 1
    Won't `touches.first` miss some use cases when nodes overlap each other as [Florian mentions for another answer](https://stackoverflow.com/questions/27922198/how-do-i-detect-if-an-skspritenode-has-been-touched#comment66915131_34466773)? – ruffin Jun 05 '20 at 22:27
7

swift 5 update

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

        for touch in touches {
            let location = touch.location(in: self)
            let touchedNode = self.nodes(at: location)
            for node in touchedNode {
                if node.name == "play_button" {
                    startGame()
                }
            }
        }
    }
Most Wanted
  • 6,254
  • 5
  • 53
  • 70
6

Implement touchesBegan method that is called when a touch begins. Alternatively you can do this in touchesEnded as well.

override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
{
    let touch = touches.anyObject() as UITouch
    let location = touch.locationInNode(self)
    let nodes = self.nodesAtPoint(location)

    for node in nodes
    {
        if node.name == "youNodeName"
        {
            // Node tapped
            // Do something

            break
        }
    }
}
Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
6

Swift 3 answer which embeds touch functionality in subclass of SKSpriteNode:

class SpriteSub: SKSpriteNode {

    init() {
        super.init(texture: nil, color: UIColor.red, size: CGSize(width: 50, height: 50))
        isUserInteractionEnabled = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    ...

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touch!")
    }

}
Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • Crashalot it's still making me put in the NSCoder init... I forgot how to properly override this thing x{ – Fluidity Nov 11 '16 at 22:22
  • 1
    @Fluidity yes you need that too – Crashalot Nov 11 '16 at 22:24
  • 1
    @Fluidity updated again. also UIColor.clear() from your edit is not Swift 3 code. – Crashalot Nov 11 '16 at 22:24
  • Crashalot yeah I caught that too my bad – Fluidity Nov 11 '16 at 22:25
  • Crashalot I'm starting to feel like an idiot now because I know I've done this before.... `let sprite = SpriteSub()` is getting me a bunch of nothing, and autocomplete shows nothing other than the `coder:` init... – Fluidity Nov 11 '16 at 22:32
  • 1
    @Fluidity it's nothing because UIColor.clear is transparent. Try UIColor.red. – Crashalot Nov 11 '16 at 22:40
  • Crashalot yeah Ima go get a beer now. I was trying to instantiate it in the SKScene field section (it wasn't the color). Upvoting now :) hashtag idiotdeveloper ----> – Fluidity Nov 11 '16 at 22:46
4

Use this piece of code to detect touch on SKSpriteNode

if(nodeAtPoint(location) == node){



}
zbz.lvlv
  • 3,597
  • 6
  • 34
  • 38
1

Update for Swift 3.0 and XCode 7.3.1. I've got a SKShapeNode that I've derived to a new class and inserted it on the Scene. When I want to detect this object it I check as follows:

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

    for touch in touches {

        let location = touch.locationInNode(self)
        let nodes = self.nodesAtPoint(location)

        for node in nodes
        {
            if node is SKNodeDerivedNode
            {
                NSLog("Touch a SKNodeDerivedNode")
                break
            }
        }
    }
}
bownie
  • 1,608
  • 15
  • 21
1

If you have still not got your sprite to work even after subclassing SKSpriteNode, you most likely forgot to add node.isUserInteractionEnabled = true when you initialise it!

This allows touchesBegan(_:with:) to be called, as you can now interact with the node.


Example:

node = MySprite(texture: texture, size: size)
node.isUserInteractionEnabled = true
George
  • 25,988
  • 10
  • 79
  • 133
0

This is the way I use in Swift 4 to find if there is a touch in a specific type of node:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else {
        return
    }
    let touchPosition = touch.location(in: self)
    let touchedNodes = nodes(at: touchPosition)
    for node in touchedNodes {
        if let nodoTouched = node as? YourNodeType {
            // touched!
        }
    }
}
abanet
  • 1,327
  • 17
  • 22