illuminatedtype illuminatedtype - 6 months ago 27
Swift Question

Cannot disable, then reenable touch, after an SKAction animation

I am working on an interactive, animated scene. I want all touches on the scene to be disabled on entry. Then, once the objects (which are subclassed nodes) in the scene finish rotating/moving, I want to re-enable all touches on the screen to allow interaction. I have disabled user interaction using this code:

override func didMove(to view: SKView) {
setupNodes()
view?.isUserInteractionEnabled = false
spinLocations()
}


This is the code, within the scene file, for spinLocations:

func spinLocations() {
var allLocationArrays = [[String : CGPoint]]()
var previousArray = hiddenLocationPositions
for _ in 0...SearchConstant.numSpins {
let freshArray = generateNewLocationArray(previous: previousArray)
allLocationArrays.append(freshArray)
previousArray = freshArray
}
for (item, _) in hiddenLocationPositions {
let node = fgNode.childNode(withName: item) as! LocationNode
node.spin(position: allLocationArrays) // this is function below
}
hiddenLocationPositions = previousArray
}


This is the code for the animations in the node class:

func spin(position: [[String : CGPoint]]) {
var allActions = [SKAction]()
for array in position {
let action = SKAction.move(to: array[self.name!]!, duration: 2.0)
allActions.append(action)
}
let allActionsSeq = SKAction.sequence(allActions)
self.run(SKAction.sequence([SKAction.wait(forDuration: 5.0), allActionsSeq, SKAction.run {
self.position = position[position.count - 1][self.name!]!
},]))
}


This is the code for passing back the touches to the main scene from this class:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let parent = self.parent else { return }
}


As you can see, touch is not disabled here.

I do not want to add a "waitForDuration"
SKAction
to the
runBlock
to change the view status after the previous action; I want the program to determine when the animations are finished executing and then re-enable touches.

In order to do this, I theorised using a completion handler might work, but it only re-enables touches immediately (e.g. handling a handler to spin causes the touches to be detected again). Previously, I also tried to disable the view in the
runBlock
, but of course, that is run instantaneously. How do I ensure that the touches are re-detected following the animation without using "waitForDuration."?

Answer Source

So, this is a simple example that shows how you can:

1) Disable touches completely

2) Spin a node

3) When node is done with spinning, to enable touches

Here is the code (you can copy/paste it to try how it works):

class Object:SKSpriteNode{

    func spin(times:Int,completion:@escaping ()->()) {

       let duration = 3.0
       let angle = CGFloat(M_PI) * 2.0

       let oneRevolution = SKAction.rotate(byAngle: angle , duration: duration)
       let spin = SKAction.repeat(oneRevolution, count: times)

       let sequence = SKAction.sequence([spin,SKAction.run(completion)])

       run(sequence, withKey:"spinning")
    }

}

class WelcomeScene: SKScene {


    override func didMove(to view: SKView) {


        view.isUserInteractionEnabled = false
        print("Touches Disabled")

        let object = Object(texture: nil, color: .purple, size: CGSize(width: 200, height: 200))

        addChild(object)

        object.spin(times: 3, completion: {[weak self] in

            self?.view?.isUserInteractionEnabled = true
            print("Touches Enabled")
        })

    }

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

    deinit {
        print("Welcome scene deinited")
    }
}

Here, you disable touches when scene is loaded, start spinning the object, and you pass a completion block to it... That block of code is used here:

let sequence = SKAction.sequence([spin,SKAction.run(completion)])

So after spinning, that block will be executed. Now, there are different ways to do this...Personally, I would use delegation, but I thought this can be less confusing... I can write an example for delegation too if needed, but basically, what you would do, is to set a scene as a delegate of your custom node, and notify it about spinning is done, so the scene can tell the view to re-enable the touches.