48

In this scenario, timerFunc() is never called. What am I missing?

class AppDelegate: NSObject, NSApplicationDelegate {

    var myTimer: NSTimer? = nil

    func timerFunc() {
        println("timerFunc()")
    }

    func applicationDidFinishLaunching(aNotification: NSNotification?) {
        myTimer = NSTimer(timeInterval: 5.0, target: self, selector:"timerFunc", userInfo: nil, repeats: true)
    }
}
RobertJoseph
  • 7,968
  • 12
  • 68
  • 113
  • If you use the timer's `init` you must `You must add the new timer to a run loop, using addTimer:forMode:`. It is the second sentence in the doc description. Otherwise use `scheduledTimerWithTimeInterval` which is probably what you were looking for. – Firo Jun 23 '14 at 15:30
  • Why the down votes considering several people provided different answers? – RobertJoseph Jun 23 '14 at 16:48
  • 1
    Can't tell you for sure, but it is probably because the answer is not difficult to find yourself, as I pointed out earlier. It is right in the docs. If you would have `option` clicked on your method, you would have found the solution within 5 seconds and without even leaving Xcode. – Firo Jun 23 '14 at 17:45

10 Answers10

98

You can create a scheduled timer which automatically adds itself to the runloop and starts firing:

Swift 2

NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "timerDidFire:", userInfo: userInfo, repeats: true)

Swift 3, 4, 5

Timer.scheduledTimer(withTimeInterval: 0.5, target: self, selector: #selector(timerDidFire(_:)), userInfo: userInfo, repeats: true)

Or, you can keep your current code, and add the timer to the runloop when you're ready for it:

Swift 2

let myTimer = NSTimer(timeInterval: 0.5, target: self, selector: "timerDidFire:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(myTimer, forMode: NSRunLoopCommonModes)

Swift 3, 4, 5

let myTimer = Timer(timeInterval: 0.5, target: self, selector: #selector(timerDidFire(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(myTimer, forMode: RunLoop.Mode.common)
Karen Hovhannisyan
  • 1,140
  • 2
  • 21
  • 31
Ryan
  • 3,853
  • 4
  • 28
  • 32
  • How to pause and resume this timer? – Qadir Hussain May 25 '15 at 10:24
  • myTimer.invalidate() cancels it. Other than that, NSTimer doesn't support any pause/resume functionality. You could imagine a simple subclass that adds this support by (1) noting start time, (2) noting pause time, and (3) on resume, run for t2-t1 shorter than our original timeInterval. – Ryan Jun 22 '15 at 03:35
  • 1
    Sift 3.0 syntax for the run loop thingy: RunLoop.current.add(myTimer, forMode: RunLoopMode.commonModes) – Florin Odagiu Sep 24 '16 at 12:11
  • 3
    Swift3 `RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)` – nikans May 25 '17 at 21:34
9

i use a similar approach to Luke. Only a caveat for people who are "private methods" purists:

DO NOT make callback private in Swift.

If You write:

private func timerCallBack(timer: NSTimer){

..

you will get:

timerCallBack:]: unrecognized selector sent to instance... Terminating app due to uncaught exception 'NSInvalidArgumentException'

ingconti
  • 10,876
  • 3
  • 61
  • 48
  • 4
    just add ``@objc`` to the private method and it will work also: ``@objc private func timerCallBack(timer: NSTimer){`` – Lensflare Feb 24 '16 at 16:18
  • @Lensflare's suggested answer is correct, you just need the `@objc` method decorator. – kyleturner Dec 19 '16 at 22:20
  • yes, I do know "@objc" but we are speaking of "swift" purist... so avoid all the old bargain from objc. (I loved it too..) – ingconti Oct 04 '20 at 13:13
7

NSTimer's are not scheduled automatically unless you use NSTimer.scheduledTimerWithTimeInterval:

myTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "timerFunc", userInfo: nil, repeats: true)
drewag
  • 93,393
  • 28
  • 139
  • 128
6

As Drewag and Ryan pointed out, you need to create a scheduled timer (or schedule it yourself) It's easiest to create it scheduled already with:

myTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "timerFunc:", userInfo: nil, repeats: true)

You also need to change your definition of timerFunc (and the associated selector) to take an argument and end with a ':'

func timerFunc(timer:NSTimer!) {
    ...
}
David Berry
  • 40,941
  • 12
  • 84
  • 95
  • 2
    You do not need to define it to take an argument. Either way is fine. – drewag Jun 23 '14 at 15:32
  • 1
    @drewag ok, I just remembered some discussion on SO about it not working without the ':', so I tried it out in the playground. You need the ':' if you (properly) define the callback as taking an argument. "Properly" because the `NSTimer` documentation says that the callback must take a single argument. "The selector should have the following signature: timerFireMethod: (including a colon to indicate that the method takes an argument)." It seems as if you can get away without the : or the argument, but I'd still say that my answer is correct based on the documentation :) – David Berry Jun 23 '14 at 15:43
  • 1
    the arguments are also optional in IBActions and anywhere else I've ever defined a selector (at least with a single argument). It is just the nature of the runtime. The fact that he left out the argument has nothing to do with why it is not called. Worst case it would throw an exception about not finding the selector, not fail silently. – drewag Jun 23 '14 at 15:49
6

For Swift 3

var timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ViewController.updateTimer), userInfo: nil, repeats: true);
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
  • 2
    Using the `scheduledTimer()` method will automatically add your Timer to the runloop - you don't need to add it manually. – Craig Otis Jun 16 '17 at 10:28
5

Swift 3.0 syntax for the run loop thingy:

RunLoop.current.add(myTimer, forMode: .commonModes)
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Florin Odagiu
  • 456
  • 6
  • 16
4

This is a bit of code, that demonstrates how to call a function (delayed) with AND without a parameter.

Use this in a new project in xCode (singleViewApplication) and put the code into the standard viewController:

class ViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("delayedFunctionWithoutParameter:"), userInfo: nil, repeats: false)

        let myParameter = "ParameterStringOrAnyOtherObject"

        NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: Selector("delayedFunctionWithParameter:"), userInfo: myParameter, repeats: false)
    }

    // SIMPLE TIMER - Delayed Function Call
    func delayedFunctionWithoutParameter(timer : NSTimer) {
        print("This is a simple function beeing called without a parameter passed")
        timer.invalidate()
    }

    // ADVANCED TIMER - Delayed Function Call with a Parameter
    func delayedFunctionWithParameter(timer : NSTimer) {

        // check, wether a valid Object did come over
        if let myUserInfo: AnyObject = timer.userInfo {
            // alternatively, assuming it is a String for sure coming over
            // if let myUserInfo: String = timer.userInfo as? String {
            // assuming it is a string comming over
            print("This is an advanced function beeing called with a parameter (in this case: \(myUserInfo)) passed")
        }

        timer.invalidate()
    }
}

Notice, that in any case you should implement the delayed function with the parameter (timer : NSTimer) to be able to invalidate (terminate, end) the timer. And with the passend "timer" you have also access to the userInfo (and there you can put any Object, not only String-Objects, as well collection types such as arrays and dictionaries).

Original Apples documentations says "" -> The timer passes itself as the argument, thus the method would adopt the following pattern: - (void)timerFireMethod:(NSTimer *)timer Read fully -> here

LukeSideWalker
  • 7,399
  • 2
  • 37
  • 45
3

Since this thread made me try to put the timer on a RunLoop myself (which solved my problem), I post my specific case as well - who knows maybe it helps somebody. My timer is created during app start up and initialisation of all the objects. My problem was that, while it did schedule the timer, it still never fired. My guess is, this was the case because scheduledTimerWithTimeInterval was putting the timer on a different RunLoop during startup of the App. If I just initialise the timer and then use NSRunLoop.mainRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode) instead, it works fine.

3

With swift3, you can run it with,

var timer: Timer?
func startTimer() {

    if timer == nil {
        timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.loop), userInfo: nil, repeats: true)
    }
}

func stopTimer() {
    if timer != nil {
        timer?.invalidate()
        timer = nil
    }
}

func loop() {
    //do something
}
LF00
  • 27,015
  • 29
  • 156
  • 295
2

To do it with the method the OP suggests, you need to add it to a run loop:

myTimer = NSTimer(timeInterval: 5.0, target: self, selector:"timerFunc", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(myTimer, forMode:NSDefaultRunLoopMode)

The documentation also says that the target should take an argument, but it works without it.

func timerFireMethod(timer: NSTimer) { }
Grimxn
  • 22,115
  • 10
  • 72
  • 85