Search code examples
swiftsprite-kitnstimer

Pause NSTimer on home-button-press and start them again once the app is in foreground


I've been searching for a solution to pause my SpriteKit game when the user "tabs down" the game. So far I found a solution where you use SKAction's instead of NSTimer's, this works as long as the time between actions stays the same. However, my NSTimer's changes in speed. So I need to find another solution.

I have a bunch of NSTimer's located in GameScene -> didMoveToView

NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("SpawnBullets"), userInfo: nil, repeats: true)

NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("SpawnMeteors"), userInfo: nil, repeats: true)

NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("onTimer:"), userInfo: nil, repeats: true)

Now, how would I simply pause them when the app enters background?

EDIT: Added my timeInterval increase-speed-function

func onTimer(timer: NSTimer) {
    var goodTimes = time / 20

    if (goodTimes > 1.8){
        goodTimes = 1.8
    }

    timer.fireDate = timer.fireDate.dateByAddingTimeInterval(timeInterval - goodTimes)

    self.runAction(SKAction.sequence([SKAction.runBlock(SpawnRocks), SKAction.waitForDuration(goodTimes / 2), SKAction.runBlock(SpawnPowerUp)]))
}

Solution

  • Pausing of NSTimer is not a native feature in objective-c or swift. To combat this, you need to create an extension, which I happen to have created and will share for you. This will work for both OS X and iOS

    import Foundation
    import ObjectiveC
    
    #if os(iOS)
        import UIKit
    #else
        import AppKit
    #endif
    
    
    private var pauseStartKey:UInt8 = 0;
    private var previousFireDateKey:UInt8 = 0;
    
    extension NSTimer
    {
        private var pauseStart: NSDate!{
            get{
                return objc_getAssociatedObject(self, &pauseStartKey) as? NSDate;
    
            }
            set(newValue)
            {
                objc_setAssociatedObject(self, &pauseStartKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN);
            }
        }
    
        private var previousFireDate: NSDate!{
            get{
                return objc_getAssociatedObject(self, &previousFireDateKey) as? NSDate;
    
            }
            set(newValue)
            {
                objc_setAssociatedObject(self, &previousFireDateKey,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN);
            }
        }
    
    
        func pause()
        {
            pauseStart = NSDate();
            previousFireDate = self.fireDate;
            self.fireDate = NSDate.distantFuture() ;
        }
    
        func resume()
        {
            if(pauseStart != nil)
            {
                let pauseTime = -1 * pauseStart.timeIntervalSinceNow;
                let date = NSDate(timeInterval:pauseTime, sinceDate:previousFireDate );
                self.fireDate = date;
            }
    
        }
    }
    

    Then when you need to use it, simply call timer.pause() and timer.resume() You of course have to keep track of your timers in your gamescene object to do this, so make sure that timer is a variable accessible for the entire class, and when making your timer, you do timer = NSTimer.schedule...

    At the beginning of your class:

    var spawnBulletsTimer : NSTimer?;
    var spawnMeteorsTimer : NSTimer?;
    var onTimer: NSTimer?;
    

    When creating the timers:

    spawnBulletsTimer = NSTimer.scheduledTimerWithTimeInterval(0.2, target:     self, selector: Selector("SpawnBullets"), userInfo: nil, repeats: true)
    
    spawnMeteorsTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("SpawnMeteors"), userInfo: nil, repeats: true)
    
    onTimer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("onTimer:"), userInfo: nil, repeats: true)
    

    Then when you need to pause:

    onTimer?.pause() 
    spawnBulletsTimer?.pause()
    spawnMeteorTimer?.pause()
    

    Then when you need to resume:

    onTimer?.resume() 
    spawnBulletsTimer?.resume()
    spawnMeteorTimer?.resume()