Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SKAction runAction does not execute completion block

A SKSpriteNode is child of a SKNode and is placed on an array of SKSpriteNode for storage purposes.

This SKSpriteNode is deleted using an animation. At the end of this animation a completion block is executed to perform some statements...

The deletion must occurred both in the SKSpriteNode parent’s and in the array. Depending on the order of these 2 deletions the result is correct or not:

  • if SKSpriteNodeis deleted from 1/ the array then 2/ from the SKNode parent’s, the completion block is executed.
  • if the inverse order, 1/ SKNode parent’s then 2/ the array, the completion block is not executed.

Why this behavior?

for position in listOfPositions {

   theSprite:SKSpriteNode = theGrid[position]

   /// the SKSpriteNode referenced by theSprite :
   /// - belongs to an array of SKSpriteNode: theGrid
   /// - belongs to a SKNode: theGameLayer
   ///
   /// In other words this SKSpriteNode is referenced twice
   ///

   let theActions = SKAction.sequence([
      /// Some actions here
      /// ...

      /// Remove theSprite from the Grid
      /// - position is an instance of a structure of my own
      /// - theGrid is accessed via a subscript
      ///
      SKAction.runBlock({self.theGrid[position] = nil}),

      /// remove theSprite from it's parent
      SKAction.removeFromParent(),
   ])

   theSprite.runAction(theActions,completion:{NSLog("Deleted")})
}

The completion message is displayed.

Now if removeFromParent is placed before the remove from theGrid action, as follow, the completion does not execute:

let theActions = SKAction.sequence([
   /// Some actions here
   /// ...

   /// remove theSprite from it's parent
   SKAction.removeFromParent(),

   /// remove theSprite from the Grid
   SKAction.runBlock({self.theGrid[position] = nil}),
])
like image 300
Dominique Vial Avatar asked Jun 02 '15 14:06

Dominique Vial


Video Answer


1 Answers

From the SKAction Reference:

An SKAction object is an action that is executed by a node in the scene (SKScene)...When the scene processes its nodes, actions associated with those nodes are evaluated.

In other words, the actions for a node is run if and only if that node is in the scene. By calling removeFromParent, you remove the node from the scene, the runBlock action is never called (since the node is no longer in the scene), and thus the sequence is never finished. Since the sequence doesn't finish, the completion block doesn't get called.

I would suggest moving the removeFromParent call to the completion block for safety's sake. Something like this feels safer:

for position in listOfPositions {

   let theSprite: SKSpriteNode = theGrid[position]

   /// the SKSpriteNode referenced by theSprite :
   /// - belongs to an array of SKSpriteNode: theGrid
   /// - belongs to a SKNode: theGameLayer
   ///
   /// In other words this SKSpriteNode is referenced twice
   ///

   let theActions = SKAction.sequence([
      /// Some actions here
      /// ...

      /// Remove theSprite from the Grid
      /// - position is an instance of a structure of my own
      /// - theGrid is accessed via a subscript
      ///
      SKAction.runBlock({self.theGrid[position] = nil})
   ])

   theSprite.runAction(theActions) {
      /// remove theSprite from it's parent
      /// Might need to weakly reference self here
      theSprite.removeFromParent(),
      NSLog("Deleted")
   }
}

TL;DR
The sequence doesn't complete, so the completion block for sequence doesn't get called.

like image 158
Ben Kane Avatar answered Nov 02 '22 07:11

Ben Kane