81

I want to add a score to the top of my scene in the game I am working on. The score is going to based on how long you last, and will increase every second. Thanks for the help in advance!

import SpriteKit

class easyScene: SKScene {
    let scrollBarEasyBottom = SKSpriteNode(imageNamed: "scrollBarEasyBottom")
    let scrollBarEasyTop = SKSpriteNode(imageNamed: "scrollBarEasyTop")
    let ball = SKSpriteNode(imageNamed: "ball")
    var origSBEBpositionX = CGFloat(0)
    var origSBETpositionX = CGFloat(0)
    var maxSBEBX = CGFloat(0)
    var SBEBSpeed = 5
    var maxSBETX = CGFloat(0)
    var SBETSpeed = 5
    var score = 0
    var timer: NSTimer?
    
    var scoreText = SKLabelNode(fontNamed: "Kailasa")
    
    override func didMoveToView(view: SKView) {
        println("Easy Scene is the location")
        self.backgroundColor = UIColor.blackColor()
        self.scrollBarEasyBottom.position = CGPoint(x:0, y:270)
        self.addChild(self.scrollBarEasyBottom)
        self.scrollBarEasyBottom.yScale = 0.2
        self.origSBEBpositionX = self.scrollBarEasyBottom.position.x
        // end scrollBarEasyBottom
        self.scrollBarEasyTop.position = CGPoint(x:20, y:400)
        self.addChild(self.scrollBarEasyTop)
        self.scrollBarEasyTop.yScale = 0.2
        self.origSBETpositionX = self.scrollBarEasyTop.position.x
        // end scrollBarEasyTop
        self.ball.position = CGPoint(x:40, y:293)
        self.addChild(self.ball)
        self.ball.yScale = 0.17
        self.ball.xScale = 0.17
        // end ball
        self.maxSBEBX = self.scrollBarEasyBottom.size.width - self.frame.size.width
        self.maxSBEBX *= -1
        self.maxSBETX = self.scrollBarEasyTop.size.width - self.frame.size.width
        self.maxSBETX *= -1
        //
        self.scoreText.text = "0"
        self.scoreText.fontSize = 60
        self.scoreText.position = CGPoint(x: CGRectGetMidX(self.frame), y: 500)
        self.scoreText.text = String(self.score)
        self.addChild(self.scoreText)
        timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("scoreIncrease") , userInfo: nil, repeats: true)
        func scoreIncrease (){
            score++
            println(score)
        }
    }

    override func update(currentTime: NSTimeInterval) {
        if self.scrollBarEasyBottom.position.x <= maxSBEBX + 1200 {
            self.scrollBarEasyBottom.position.x = self.origSBEBpositionX
        }
        if self.scrollBarEasyTop.position.x <= maxSBETX + 1200 {
            self.scrollBarEasyTop.position.x = self.origSBETpositionX
        }
        
        scrollBarEasyBottom.position.x -= CGFloat(self.SBEBSpeed)
        scrollBarEasyTop.position.x -= CGFloat(self.SBETSpeed)
        // moving bars
        var degreeRotation = CDouble(self.SBEBSpeed) * M_PI / 180
        self.ball.zRotation -= CGFloat(degreeRotation)
        //rotate ball
    }        
}

After running this code, I always get an

unrecognized selector sent to instance error

aturan23
  • 4,798
  • 4
  • 28
  • 52
Ryan Allen
  • 883
  • 1
  • 7
  • 9
  • 3
    All these answers are not that helpful actually. In SpriteKit you should consider using SKactions for time related stuff. Search about this on SO. In general, a Timer (NSTimer) doesn't respect node's, scene's, or view's paused state, so it can lead into certain issues. On the other side, SKActions are paused automatically if node, scene or a view is paused during a game or at certain interruptions like a phone call etc. – Whirlwind Feb 23 '17 at 17:24

7 Answers7

128

You can use one like this:

var timer = NSTimer()

override func viewDidLoad() {
    scheduledTimerWithTimeInterval()
}

func scheduledTimerWithTimeInterval(){
    // Scheduling timer to Call the function "updateCounting" with the interval of 1 seconds
    timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateCounting"), userInfo: nil, repeats: true)
}
      
func updateCounting(){
    NSLog("counting..")
}

Swift 3:

var timer = Timer()

override func viewDidLoad() {               // Use for the app's interface
    scheduledTimerWithTimeInterval()
}

override func didMove(to view: SKView) {    // As part of a game
    scheduledTimerWithTimeInterval()
}

func scheduledTimerWithTimeInterval(){
    // Scheduling timer to Call the function "updateCounting" with the interval of 1 seconds
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
}

@objc func updateCounting(){
    NSLog("counting..")
}

Swift 5:

Note: this solution is compatible with iOS 10.0+.

// If needing to check for iOS compatibility use
// if #available(iOS 10.0, *) {code}

var timer = Timer()

override func viewDidLoad() {
    self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
        updateCounting()
    })
}

func updateCounting(){
    print("counting...")
}

You can then invalidate (stop) the timer using:

timer.invalidate()
ee.bt
  • 308
  • 1
  • 4
  • 9
Nurdin
  • 23,382
  • 43
  • 130
  • 308
  • 7
    Thanks a lot! I have a question, how can I stop timer? – senty Dec 24 '15 at 14:12
  • 27
    timer.invalidate() – palme Mar 02 '16 at 00:20
  • Does this affect the performance of the app or the experience for the user? As in will the user be able to interact with the app normally or will it lag/be unresponsive while it is executing? Thanks. – F.A Jul 25 '18 at 10:54
  • @F.A it depends on what you're executing. If it's a slow block of code then it will effect your app's performance and you may instead want to consider running your block of code on a background thread/queue – David Zorychta Aug 07 '18 at 22:15
  • 1
    Remark: Seems that `NSTimer` has been **renamed** to `Timer`. – ch271828n Aug 24 '20 at 01:58
34

There is something called NSTimer in swift which could solve your problem. I have given an example like how you can use it. Just customise it for your purpose.

var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, 
                                                   target: self, 
                                                   selector: Selector("yourMethodToCall"), 
                                                   userInfo: nil, 
                                                   repeats: true)

Add this line to the place where you need to call your function repeatedly.

  1. The 1.0 refers to 1 second.
  2. Change the selector to call yourMethodName
  3. repeats is set to true to call that function every second.

Try this out and let me know if your are stuck somewhere. Thanks.

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Bullionist
  • 2,070
  • 3
  • 25
  • 42
  • About mid-way through my code I think I tried to do this. I am probably using it wrong somehow because I'm still a beginner at this, but if would be great if you could take a look at that. You may have missed it when you first looked at it. Thanks! – Ryan Allen May 07 '15 at 01:29
  • 2
    Try to put your `scoreIncrease` method outside the `didMoveToView` method. – Bullionist May 07 '15 at 01:29
  • That fixed my problem thank you so much! Now my issue is that even though my output prints "score" every second, the number on the game screen still remains at 0. – Ryan Allen May 07 '15 at 11:32
  • Where can I find this? Sorry I'm pretty new to this so bare with me! – Ryan Allen May 07 '15 at 21:24
  • hi @ShaikMDAshiq I am have declared var timer as a global variable in my tableView and get the error `'NSObject -> () -> TableViewController does not conform to expected type 'AnyObject'` Where should I declare this variable? – SamYoungNY Oct 12 '15 at 21:52
11

Swift 3

find this solution it worked for me

weak var timer: Timer?
var timerDispatchSourceTimer : DispatchSourceTimer?

func startTimer() {
    if #available(iOS 10.0, *) {
        timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [weak self] _ in
            // do something here

        }

    } else {
        // Fallback on earlier versions
        timerDispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
        timerDispatchSourceTimer?.scheduleRepeating(deadline: .now(), interval: .seconds(60))
        timerDispatchSourceTimer?.setEventHandler{
                // do something here

        }
        timerDispatchSourceTimer?.resume()
    }
}

func stopTimer() {
    timer?.invalidate()
    //timerDispatchSourceTimer?.suspend() // if you want to suspend timer 
    timerDispatchSourceTimer?.cancel()
}

// if appropriate, make sure to stop your timer in `deinit`
deinit {
    stopTimer()
}
Amr Angry
  • 3,711
  • 1
  • 45
  • 37
  • 2
    Using NSTimer in SpriteKit is not a preferred way for time based actions. It doesn't respect node's, scene's or view's paused state and can lead into certain issues in some situations. Using SKActions or update: method is a better alternative. – Whirlwind Feb 23 '17 at 17:20
  • @Whirlwind i have never use spirteKit , but it's time to learn about it :) thanks for inspiration – Amr Angry Feb 24 '17 at 21:48
10

I prefer

var timer: Timer?

override func viewDidLoad() {
    super.viewDidLoad()          

    timer =  Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in                
        // Do what you need to do repeatedly         
    }
}

To stop it:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if timer != nil {
        timer?.invalidate()
        timer = nil
    }
}
Tomas
  • 866
  • 16
  • 21
Ricardo
  • 2,086
  • 25
  • 35
8

Xcode 10.2 Swift 5:

override func viewDidLoad() {
    super.viewDidLoad()
    // ...
    Timer.scheduledTimer(timeInterval: 8.0, target: self, selector: Selector(("your @obcj func name")), userInfo: nil, repeats: true)
}

//Anywhere in the same view controller to stop the loop:
Timer.cancelPreviousPerformRequests(withTarget: your @obcj func name())
pkamb
  • 33,281
  • 23
  • 160
  • 191
Brian M
  • 331
  • 3
  • 10
4

I don't think you need NSTimer for this.

Since you are using SpriteKit, I am going to suggest simplest solution in my opinion:

Declare a variable var prevScoreCalcTime:TimeInterval = 0

Inside of update func in your GameScene set it up like below:

override func update(_ currentTime: TimeInterval) {
    if currentTime - prevScoreCalcTime > 1 {
        prevScoreCalcTime = currentTime
        // Any function you put here will execute every second
    }
}

Good luck!

Vetuka
  • 1,523
  • 1
  • 24
  • 40
  • 2
    This might not necessarily be the most popular way of doing it, but if your using SpriteKit or something with a polling function, its a *safe* way to do thing. It'll never be triggered after the container calling it is destroyed. – Shayne Apr 16 '19 at 05:49
0

// For running a piece of code every second

///Runs every second, to cancel use: timer.invalidate()
@discardableResult public static func runThisEvery(
    seconds: TimeInterval,
    startAfterSeconds: TimeInterval,
    handler: @escaping (CFRunLoopTimer?) -> Void) -> Timer {
    let fireDate = startAfterSeconds + CFAbsoluteTimeGetCurrent()
    let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, seconds, 0, 0, handler)
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, CFRunLoopMode.commonModes)
    return timer!
}
Pratyush Pratik
  • 683
  • 7
  • 14