0

I would need help with this while loop - what I'm trying to do is slow down the whole process of removing and adding new circles while radius is changing every time this happens. I'm becoming really desperate, I've tried using both dispatch_after and sleep inside the loop (which I found online) but neither of them is suitable, they basically stop the whole app. If I put them in the while loop, nothing happens. Thanks in advance!

while radius < 100 {

    self.removeAllChildren()
    addCircle()
    radius++
    print(radius)            
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • `[NSThread sleepForTimeInterval:number of seconds you want to wait before next iteration];` inside the loop would do the trick. This is in objective C. Look for Swift equivalent of it. – NSNoob Dec 09 '15 at 11:25
  • Here is the [Apple Documentation](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSThread_Class/index.html#//apple_ref/occ/clm/NSThread/sleepForTimeInterval:) for it. It will put whatever thread it is running on to sleep. If i were you, i would dispatch the loop to another thread and call this there, putting that thread to sleep while enjoying full working mainthread. – NSNoob Dec 09 '15 at 11:29
  • But wouldn't it work if loop was dispatched to another thread hence putting that thread to sleep instead of mainthread? (Since I havent worked much with GCD and threads I am not sure, sorry if it is a naive question) @DanielT. – NSNoob Dec 09 '15 at 11:32
  • @DanielT. [This Code](http://paste.ubuntu.com/13856322/) is working for me. I can work on my app, while loop keeps running in background with a delay on every iteration – NSNoob Dec 09 '15 at 11:37
  • All UIView code needs to be executed on the main thread. – Daniel T. Dec 09 '15 at 11:40
  • @DanielT. Still no reason to say that my suggested approach wouldn't have worked. For UI Updates she could simply add `dispatch_async(dispatch_get_main_queue(), ^(void){ });` block inside. – NSNoob Dec 09 '15 at 11:42
  • Exactly, you forgot to mention that. – Daniel T. Dec 09 '15 at 11:45
  • 2
    The OP uses SpriteKit obviously ... So why not using SKAction sequence (which will wait & add node). Using GCD is completely unnecessary in this situation. Also NSTimer or dispatch_... methods don't respect scene's (or node's) paused state, and SKAction or update: method and its passed currentTime parameter is preferred way for time related actions in SpriteKit. – Whirlwind Dec 09 '15 at 11:57

2 Answers2

3

Basically you just need to do few simple things:

Here is the example of how you can do it. The important part is action sequence. You create the step above, and repeat it forever. Each time you check radius value and based on that you stop the action (remove it by the key). And that's it. You can change spawning speed by changing action's duration parameter.

Using NSTimer might seem like an easier solution, but NSTimer doesn't respect node's (or scene's or view's) paused state. So, imagine this situation:

  • You start spawning of nodes
  • User receive phone call and app automatically goes to background

Because NSTimer is not automatically paused, the nodes will continue with spawning. So you have to take an additional step and invalidate/restart timer by your self. When using SKAction, this is done automatically. There are some other flaws of using NSTimer in SpriteKit, search SO about all that, there are some posts which covering all this.

import SpriteKit

class GameScene: SKScene{

    var radius:UInt32 = 0

    override func didMoveToView(view: SKView) {

        startSpawning()
    }

    func startSpawning(){

        let wait = SKAction.waitForDuration(0.5)

       // let wait = SKAction.waitForDuration(1, withRange: 0.4) // randomize wait duration

        let addNode = SKAction.runBlock({

            [unowned self] in //http://stackoverflow.com/a/24320474/3402095 - read about strong reference cycles here

            if(self.radius >= 30){ 

                if self.actionForKey("spawning") != nil {

                    self.removeActionForKey("spawning")
                }
            }

            self.radius++
            let sprite = self.spawnNode()
            sprite.position = CGPoint(x: Int(arc4random()) % 300,  y: Int(arc4random()) % 300) // change this to randomize sprite's position to suit your needs

            self.addChild(sprite)
        })
        //wait & add node   
        let sequence = SKAction.sequence([wait, addNode])
        //repeat forever
        runAction(SKAction.repeatActionForever(sequence), withKey: "spawning")

    }

    func spawnNode()->SKSpriteNode{
        let sprite = SKSpriteNode(color: SKColor.purpleColor(), size: CGSize(width: 50, height: 50))
        //Do sprite initialization here
        return sprite
    }
}
Whirlwind
  • 14,286
  • 11
  • 68
  • 157
0

The sleep stops the whole app because you are running the loop on the main thread.

You can solve this problem in one of two ways...

1) Use an NSTimer. Set it to the amount of time you want to delay. Put the body of the loop in the timer's trigger function and disable the timer when it has been called enough times. This is the best solution.

2) Wrap the loop in a dispatch async block to a background thread and put a sleep in the loop. If you do this though, you will have to wrap the UI code in another dispatch async block that comes back to the main thread.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • 1
    Please look into SKAction. That is what we would use in SpriteKit. SKAction plays well with the update loop that SpriteKit uses, whereas GCD and NSTimer do not. You're right that you _could_ do it this way, but it would definitely not be best or good practice for SpriteKit. – Ben Kane Dec 09 '15 at 15:32