Reparenting an SKNode in its own runAction:completion: block corrupts the scene graph
| Originator: | sartak | ||
| Number: | rdar://16534399 | Date Originated: | 2014-04-05 |
| Status: | Closed | Resolved: | 2014-06-03 |
| Product: | iOS SDK | Product Version: | 7.1 |
| Classification: | UI | Reproducible: | Always |
Summary:
If you run an action on an SKNode, and in the completion block for that `runAction:` you remove and readd that SKNode, it results in a slightly corrupted scene graph.
This is especially important in cases where that SKNode is being moved to a different parent (e.g. to move the node out of the scope of an SKCropNode or SKEffectNode).
This problem occurs regardless of whether you use either:
1. `[SKAction removeFromParent]` as the final item in an action sequence before the completion block, or
2. `[node removeFromParent]` in the completion block
Steps to Reproduce:
1. Create an SKNode and add it to the scene.
2. Use `[node runAction:... completion:^{ ... }]` and in that completion block, `[node removeFromParent]` and `[otherNode addChild:node]`. OR, include an `[SKAction removeFromParent]` instead of `[node removeFromParent]`; that makes no difference.
Create a new iOS Xcode project (or use the attached tarball) using the Sprite Kit template. Replace the scene class's implementation with the following code. Note the if-statement inviting you to test the workaround.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
[self addChild:sprite];
SKAction *moveRemove = [SKAction sequence:@[
[SKAction moveTo:CGPointMake(size.width/2, size.height/2) duration:2],
[SKAction removeFromParent],
]];
[sprite runAction:moveRemove completion:^{
if (0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self readdNode:sprite];
});
}
else {
[self readdNode:sprite];
}
}];
}
return self;
}
-(void)readdNode:(SKNode *)node {
[self addChild:node];
node.position = CGPointZero;
NSLog(@"scene (%d children) contains node? %@", self.children.count, [self.children containsObject:node] ? @"YES" : @"NO");
NSLog(@"node.scene = %@", node.scene);
}
Expected Results:
The node will be correctly removed and re-added into the scene graph.
Actual Results:
The node is not completely added to the scene graph.
The SKNode's scene property is correctly reported as the SKScene subclass instance. The SKNode's new parent correctly reports the node as being a child.
However, the SKNode is not rendered to the screen. Also, SKView's `showsFPS` indicator incorrectly reports 0 children.
Version:
iOS 7.1 (11D167)
Notes:
The workaround I've been using is to wrap the contents of the completion block in a `dispatch_async(dispatch_get_main_queue(), ^{ ... })` invocation. This presumably gives the action subsystem enough time to finish tearing down the action which results in the correct behavior.
Configuration:
iPhone 5S
iOS Simulator
Comments
Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!
sartak
Attached a sample Xcode project to demonstrate the problem.
Note the if-statement on RPGMyScene.m line 24 inviting you to test the workaround.