SpriteKit: How to detect touches on node that began outside of node - ios

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?

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
}

Related

Exceeding the maximum number of touches makes the program not to call the touchesEnded method?

Ive just started learning programming in general and ran into a problem:
I have read some articles about that the iPhones are only capable of tracking 5 touches or so... But I realised that when I touch the screen with e.g. 7 fingers at once, my program-let stops working.
So, anyone knows, which parts of the code malfunctions when I am touching the screen way too many times at once?
(I am a newbie.)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
touchLocation = touch.locationInNode(self)
nrTouches += touches.count
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
nrTouches -= touches.count
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first!
touchLocation = touch.locationInNode(self)
}
override func update(currentTime: CFTimeInterval) {
if nrTouches > 0 {
touchingLabel.text = "touching"
} else {
touchingLabel.text = "not touching"
}
}
So, in the case I am touching with 7 fingers at once, "touching" will be displayed all the time.
tankyuu
You're missing touchesCancelled, which gets called when the 6th finger is put down:
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
if let touches = touches {
nrTouches -= touches.count // same logic as touchesEnded
}
}
An interesting note: The iPad can handle 11 touch events. More on that here.

Set<UITouch> not working in iOS Swift game

I am making a game similar to Fruit Ninja using Swift 2.0 in Xcode v7.0. However, in the touchesBegan, touchesMoved, and touchesCancelled override functions, I am getting the three following errors:
"Value of type 'Set' has no member 'anyObject'" comes up twice and
"Value of optional type 'Set' not unwrapped; did you mean to use "!" or "?"?" comes once.
Here is the code
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
activeSlicePoints.append(location)
redrawActiveSlice()
activeSliceBG.removeAllActions()
activeSliceFG.removeAllActions()
activeSliceBG.alpha = 1
activeSliceFG.alpha = 1
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.anyObject() as UITouch
let location = touch.locationInNode(self)
activeSlicePoints.append(location)
redrawActiveSlice()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
activeSliceBG.runAction(SKAction.fadeOutWithDuration(0.25))
activeSliceFG.runAction(SKAction.fadeOutWithDuration(0.25))
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
touchesEnded(touches, withEvent: event)
}
Here is an image of on what lines the errors appear
All help appreciated. Thanks in advance.
let touch = touches.first as? UITouch
This will get you the first touch object.
Use it like:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
// ...
}
super.touchesBegan(touches, withEvent:event)
}

SpriteKit custom UIScrollView with acceleration

Basically I need to create a UIScrollView in my SpriteKit project but i'm having a lot of problem adding SKButtons (custom SKNode class for button management). So I proceeded to create a scrollable SKNode with touch gesture but, obviously, this bar won't have the native UIScrollView acceleration: the feature I was looking for.
So tried to overtake this problem by adding a native UIScrollView and catch every change of position, like this:
using this code:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[blueNode setPosition:CGPointMake(blueNode.position.x, scrollerUI.contentOffset)];
}
This works right but foolishly I forgot that if I add buttons, the touch gesture won't recognize the button's touch event! (The UIScrollView has the priority).
Maybe is just a stupid question but I don't really know how to figure it out. Maybe programming my own acceleration methods?
I think I came up with a solution to handle touches. Since the UIScrollView eats all the touches to allow it to scroll you need to pass them to the SKScene. You can do that by subclassing UIScrollView:
class SpriteScrollView: UIScrollView {
var scene: SKScene?
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesBegan(touches, withEvent: event)
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesMoved(touches, withEvent: event)
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
scene?.touchesCancelled(touches, withEvent: event)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
scene?.touchesEnded(touches, withEvent: event)
}
}
Then your scene needs to see if the touch hits any nodes and appropriately send the touches to those nodes. So in your SKScene add this:
var nodesTouched: [AnyObject] = []
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
let location = (touches.first as! UITouch).locationInNode(self)
nodesTouched = nodesAtPoint(location)
for node in nodesTouched as! [SKNode] {
node.touchesBegan(touches, withEvent: event)
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesMoved(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesMoved(touches, withEvent: event)
}
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesCancelled(touches, withEvent: event)
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
for node in nodesTouched as! [SKNode] {
node.touchesEnded(touches, withEvent: event)
}
}
Note this is for single touches and does not handle errors well, you'll need to wrap some of the forced down casting in if statements if you want to do this properly
I hope this helps someone even though the question is months old.
One possible solution would be to add tap gesture to scene:
override func didMoveToView(view: SKView) {
let tap : UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapped:"))
view.addGestureRecognizer(tap)
}
func tapped(tap : UITapGestureRecognizer) {
let viewLocation = tap.locationInView(tap.view)
let location = convertPointFromView(viewLocation)
let node = nodeAtPoint(location)
if node.name == "lvlButton" {
var button = node as! SKButton
button.touchBegan(location)
}
}

touchesBegan called many times

I have strange issue with touchesBegan handler being called more than necessary.
If I tapped the UIView (UIButton) fast 2 times, touchesBegan was called 3 times.
I solved the issue with simple time measurement but I am still interested what would be the reason for this kind of behaviour?
Here is the code (with already added time-checking):
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
if let t:UITouch = touches.anyObject() as? UITouch
{
if !CGRectContainsPoint(CGRectMake(0, 0, self.frame.width, self.frame.height), t.locationInView(self))
{
touchesCancelled(touches, withEvent: event)
}
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
ForCancelTouch = false
setupButtonGUI(true)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
if !ForCancelTouch
{
if abs(LastValidTouchesBeganDate.timeIntervalSinceNow) > DelayBetweenFastTapping
{
NSNotificationCenter.defaultCenter().postNotificationName(SBDCNotificationNameActionSTBMakeOneCommand, object: self, userInfo: ["tag":self.tag])
LastValidTouchesBeganDate = NSDate()
}
}
setupButtonGUI(false)
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
ForCancelTouch = true
setupButtonGUI(false)
}
Well - it seems the issue happens ONLY when clicking/testing with mouse inside iOS simulator! It looks like it is an iOS simulator bug.

Overriding touchesBegan in Swift

I am overriding:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
in Swift.
I am wondering, is this correct for getting one touch point:
var touchPoint: CGPoint! = event.touchesForView(self)?.anyObject()?.locationInView(self)
Thanks in advance!
As of iOS 8.3, touchesBegan takes a touches parameter which is a Set<NSObject>. It's now Set<UITouch>. So, you would:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: view) else { return }
// use `point` here
}
In previous iOS versions, touchesBegan was defined as a NSSet parameter, as outlined in the question, so you would:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if let touch = touches.anyObject() as? UITouch {
let touchPoint = touch.locationInView(view)
...
}
}

Resources