2

SKNode A is a parent of SKNode B.

If touches begin inside of SKNode B, the touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) function gets called.

If, however, the touches begin outside of SKNode B but move into SKNode B, the touchesMoved function never gets called.

One option is to handle all touch events inside SKNode A.

However, there are many nodes contained inside SKNode A. Ideally, we could push touch functionality into each child, as opposed to handling everything from SKNode A.

Is it possible to capture touch events that occur inside SKNode B even if they began outside of SKNode B?

Crashalot
  • 33,605
  • 61
  • 269
  • 439

1 Answers1

2

When you hit your touchesBegan method, your touch is limited to the coordinate system of that node until the point it is ended. Moving onto another node will not change the coordinate system to the new node. If B is the only node that requires the touch, then you can add a child to cover the entire scene to ensure that B gets fired.

class TouchNode : SKSpriteNode
{
    //if you need to override other inits do it here
    convenience init(withSize size:CGSize)
    {
       self.init(color:UIColor(red:0,green:0,blue:0,alpha:0.1),size: size)
       isUserInteractionEnabled = true

    }

    override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesBegan(touches:touches, withEvent:event)
    } 

    override func touchesMoved(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesMoved(touches:touches, withEvent:event)
    } 

    override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesEnded(touches:touches, withEvent:event)
    } 

    override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesCancelled(touches:touches, withEvent:event)
    } 

}

Then in your NodeB class:

required init(coder aDecoder:NSCoder)
{
    super.init(coder:aDecoder)
    DispatchQueue.main.async{
        self.addChild(TouchNode(withSize:self.scene!.size))
    }
}
override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) { 
    //do stuff
} 

override func touchesMoved(touches: Set<UITouches>, withEvent event: UIEvent?) { 
    //do stuff
} 

override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) { 
    //do stuff
} 

override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) { 
    //do stuff
} 

Edit: Misread question, but leaving the answer in case somebody else needs to go from inside the node to outside.

Think about it, you want to touch nodeB outside of nodeB, this does not make sense. It is like me poking the air and you crying that I am poking you. But if you absolutely need to go outside of the bounds, then add a child node to the sprite when you touch it that extends beyond the node itself, and be sure to transfer the touch back up to the parent

class TouchNode : SKSpriteNode
{
    //if you need to override other inits do it here
    convenience init(withSize size:CGSize)
    {
       self.init(color:UIColor(red:0,green:0,blue:0,alpha:0.1),size: size)
       isUserInteractionEnabled = true

    }

    override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesBegan(touches:touches, withEvent:event)
    } 

    override func touchesMoved(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesMoved(touches:touches, withEvent:event)
    } 

    override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesEnded(touches:touches, withEvent:event)
    } 

    override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) { 
        parent!.touchesCancelled(touches:touches, withEvent:event)
    } 

}

Then in your NodeB class:

lazy var touchNode = TouchNode(withSize:self.scene!.size)

override func touchesBegan(touches: Set<UITouches>, withEvent event: UIEvent?) { 
    addChild(touchNode)
    //do other stuff
} 
override func touchesEnded(touches: Set<UITouches>, withEvent event: UIEvent?) { 
    touchNode.removeFromParent()
    //do other stuff
} 

override func touchesCancelled(touches: Set<UITouches>, withEvent event: UIEvent?) { 
    touchNode.removeFromParent()
    //do other stuff
} 
Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • Thanks! To clarify, I agree with you, but this isn't the goal. :) The goal is to capture events *only* when they are inside of Node B. The problem is if the touches are inside B, they are ignored if they *began* outside of B (i.e., started in C then moved over to B). – Crashalot Nov 29 '17 at 18:21
  • oh, I read the question wrong, of course touchesMoved inside of node B won't get called, you can't have touchesMoved without a touchesBegan. You need to do the opposite of this answer, on your touchesmoved, check your children being touched and pass the info along – Knight0fDragon Nov 29 '17 at 18:25
  • I need to check, but i am pretty sure if you start inside of a node and move outside, the touchesMoved does not get called on the outside either, it is confined to the coordinate system of the node being touched – Knight0fDragon Nov 29 '17 at 18:32
  • If nodeB is the only node that requires touch, then this answer works with modification (just add the touchNode right after the init [doing it during the init will have scene be nil] and never remove it) – Knight0fDragon Nov 29 '17 at 18:34
  • Not your fault, it means the question wasn't worded clearly enough. :) Agreed, as mentioned in the question, it seems like the only option is for Node A to handle all touches and kick down to Node B. Was hoping to see if it was possible to put everything into Node B and keep Node A clean (because Node A needs to handle lots of other stuff). – Crashalot Nov 29 '17 at 18:50
  • Already gave you the solution for that, providing A does not require touches – Knight0fDragon Nov 29 '17 at 18:51
  • Technically you should be using scene to process all of your touches, the only good time that I know of that you really should me messing with touch inside of a node is when you are treating it like a button. – Knight0fDragon Nov 29 '17 at 18:52
  • Sorry, should have clarified: other children will require touches besides B. This part of the motivation of abstracting touch handling into B. – Crashalot Nov 29 '17 at 18:53
  • There is some complex swiping that needs to happen in the child, but it looks like pushing all touches into the scene is the only viable option at this stage. – Crashalot Nov 29 '17 at 18:53
  • Thanks anyway! You're always so generous and helpful on SpriteKit issues. – Crashalot Nov 29 '17 at 18:54
  • i would recommend using the pan gesture then to handle the swiping, and as you move across the screen, perform a hit test to determine when B gets hit – Knight0fDragon Nov 29 '17 at 18:55
  • Yup, that's the current default, was hoping there was a cleaner way. Looks like there isn't an alternative. – Crashalot Nov 29 '17 at 19:04