A completion block was never called at the end of a sequence of SKAction groups

Why has the termination never been called?

I'm terribly sorry about this, a dump of code ... because I have no idea why every part of this works, except for a call to completion.

SKAction, which does not cause its completion, all but completion. It:

curtain.run(fadeMoveWipeAndReveal, completion: {onDone(), print("I'm done!!!!")}) 

In the following class:

 import SpriteKit class WipeCurtain: SKSpriteNode { var wipeCurtainBase: SKSpriteNode? var returnable = SKNode() var moveRight = SKAction() var node = SKNode() init( color: SKColor, size: CGSize, time: TimeInterval, reveal: @escaping () -> (), onDone: @escaping () -> ()) { super.init(texture: nil, color: color, size: size) wipeCurtainBase = SKSpriteNode(color: color, size: size) let show = SKAction.run(reveal) let fadeIn = createFadeIn(duration: time) let moveRight = createMoveRight(duration: time) let wipeRight = createWipeRight(duration: time) let fadeAndMove = SKAction.group( [ fadeIn, moveRight ] ) let wipeAndReveal = SKAction.group( [ show, wipeRight ] ) let fadeMoveWipeAndReveal = SKAction.sequence( [ fadeAndMove, wipeAndReveal ] ) if let curtain = self.wipeCurtainBase { curtain.anchorPoint = CGPoint(x: 1, y: 0) curtain.position = CGPoint(x: frame.size.width, y: 0) curtain.zPosition = -1 curtain.name = "wipe" curtain.run(fadeMoveWipeAndReveal, completion: { onDone() print("I'm done!!!!") }) } } func createFadeIn(duration: TimeInterval) -> SKAction { let fadeIn = SKEase .fade( easeFunction: .curveTypeLinear, easeType: .easeTypeIn, time: duration, fromValue: 0, toValue: 1 ) return fadeIn } func createMoveRight(duration: TimeInterval) -> SKAction { let moveRight = SKEase .move( easeFunction: .curveTypeExpo, easeType: .easeTypeOut, duration: duration, origin: CGPoint( x: 0, y: 0), destin: CGPoint( x: frame.size.width, y: 0) ) return moveRight } func createWipeRight(duration: TimeInterval) -> SKAction { let wipeRight = SKEase .createFloatTween( start: 1, ender: 0, timer: duration, easer: SKEase .getEaseFunction( .curveTypeExpo, easeType: .easeTypeOut ), setterBlock: {(node, i) in node.xScale = i} ) return wipeRight } func wipeWith() -> SKNode { if let curtain = wipeCurtainBase?.copy() as! SKSpriteNode? { returnable = curtain } return returnable } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Update:

Here's the gameScene that does this job, a very easily modified version of the Xcode SpriteKit template:

 import SpriteKit import GameplayKit class GameScene: SKScene { private var swipe: WipeCurtain? override func didMove(to view: SKView) { swipe = WipeCurtain(color: .brown, size: frame.size, time: 1, reveal: self.showOrNot, onDone: self.previewer ) } func showOrNot(){ print(" Deciding to show or not.......") } func previewer(){ print("now running the previewer") } func touchDown(atPoint pos : CGPoint) { addChild(swipe!.wipeWith()) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { for t in touches { self.touchDown(atPoint: t.location(in: self)) } } } 
+5
source share
3 answers

I want to draw your attention to these few lines:

enter image description here

Violation Code:

 curtain.run(fadeMoveWipeAndReveal, completion: { onDone() print("I'm done!!!!") }) 

where you can substitute fadeMoveWipeAndReveal one of the other actions, for example just show :

 let show = SKAction.run(reveal) 

and you will see that completion is never called. What for? Since it is launched for the first time during WipeCurtain initialization, but the action is deleted before it is completed, therefore, the next time this call is called to launch through touchesBegan completion will never be called.

You can test it by setting a breakpoint on curtain.run code and running the project:

enter image description here

since you can see that the breakpoint stops the project immediately during initialization, at this point the action is deleted until it is completed.

About your workaround, this is correct, it works because you delete completion and just use SKAction.sequence , which is executed correctly every time you call it.


More : The copy method works for me, I suspect that some people have problems with it, because there are more SKEase versions around the Internet, and maybe some of them may have a problem, but this version is well demonstrated by these two screenshots below :

enter image description here

enter image description here

+2
source

Using the run (block :() → ()) action in the final sequence gets the desired result, but still does not help me understand why completion never causes a call. This is just a workaround:

 import SpriteKit class WipeCurtain: SKSpriteNode { var wipeCurtainBase: SKSpriteNode? var returnable = SKNode() var moveRight = SKAction() var node = SKNode() init( color: SKColor, size: CGSize, time: TimeInterval, reveal: @escaping () -> (), onDone: @escaping () -> ()) { super.init(texture: nil, color: color, size: size) wipeCurtainBase = SKSpriteNode(color: color, size: size) let show = SKAction.run( reveal ) let endBlock = SKAction.run( onDone ) let fadeIn = createFadeIn(duration: time) let moveRight = createMoveRight(duration: time) let wipeRight = createWipeRight(duration: time) let fadeAndMove = SKAction.group( [ fadeIn, moveRight ] ) let wipeAndReveal = SKAction.group( [ show, wipeRight ] ) let fadeMoveWipeAndReveal = SKAction.sequence( [ fadeAndMove, wipeAndReveal, endBlock ] ) if let curtain = self.wipeCurtainBase { curtain.anchorPoint = CGPoint(x: 1, y: 0) curtain.position = CGPoint(x: frame.size.width, y: 0) curtain.zPosition = -1 curtain.name = "wipe" curtain.run(fadeMoveWipeAndReveal) } } func createFadeIn(duration: TimeInterval) -> SKAction { let fadeIn = SKEase .fade( easeFunction: .curveTypeLinear, easeType: .easeTypeIn, time: duration, fromValue: 0, toValue: 1 ) return fadeIn } func createMoveRight(duration: TimeInterval) -> SKAction { let moveRight = SKEase .move( easeFunction: .curveTypeExpo, easeType: .easeTypeOut, duration: duration, origin: CGPoint( x: 0, y: 0), destin: CGPoint( x: frame.size.width, y: 0) ) return moveRight } func createWipeRight(duration: TimeInterval) -> SKAction { let wipeRight = SKEase .createFloatTween( start: 1, ender: 0, timer: duration, easer: SKEase .getEaseFunction( .curveTypeExpo, easeType: .easeTypeOut ), setterBlock: {(node, i) in node.xScale = i} ) return wipeRight } func wipeWith() -> SKNode { if let curtain = wipeCurtainBase?.copy() as! SKSpriteNode? { returnable = curtain } return returnable } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 
+4
source

.copy() does not copy blocks or locks in this way. This is either a mistake, Apple’s lack of planning, or some principle that they don’t reveal to anyone. This may be an NSObject thing, because you also cannot save blocks or use NSCoder with them (afaik).

From a preliminary Xcode 8 point of view, none of this should do anything at all AFAIK except CRASH .. You only call the initializer once (which does the actual work with the curtain ), and you don’t add it to the scene first.

I would suggest using a different approach with this or using a workaround when you came up with. Initialization of a new node may be faster than .copy() . Testing should be done to see how effective it is.

Bounty Update:

I assume that NSObject and NSCoder match SKNode ... all nodes match NSCoder, and you cannot use NSCoder with closures / blocks, at least in any case that I know about ... even trying to convert to / from NSData or NSString.

+1
source

Source: https://habr.com/ru/post/1260833/


All Articles