user3582537 user3582537 - 4 months ago 59
iOS Question

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:

enter image description here

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?

Answer

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.