Search code examples
iosswiftnstimer

NSTimer not firing property


I have set up a timer class to firer when the viewDidLoad(). I want to have a timer on multiple view controllers thoughout the app. If you have a better solution to a accurate timer on multiple views please suggest.

Viewcontroller -> One of the views that needs a timer

 override func viewDidLoad() {
 super.viewDidLoad()

    func setupTimer() {

        // Setupt the timer, this will call the timerFired method every second
        var timer = NSTimer(
            timeInterval: 1.0,
            target: self,
            selector: #selector(TestTimer.timerFired()),//<- Error Code
            userInfo: nil,
            repeats: true)
         }

The Error Code: Use of instance member 'timerFired' on type 'TestTimer',did you mean to use a value of type 'TestTimer' instead?

Timer Class -> Checks start date compared to current date/time for a accurate timer

 class TestTimer: NSTimer {

var timer   = NSTimer()
// Converter changes String into NSDate
var startDate = converter("Tue, 26 Apr 2016 09:01:00 MDT")
// Function to be fired
func timerFired() {

    let now = NSDate()
    let difference = now.timeIntervalSinceDate(self.startDate)


    // Format the difference for display
    // For example, minutes & seconds
    let dateComponentsFormatter = NSDateComponentsFormatter()
    dateComponentsFormatter.stringFromTimeInterval(difference)
    print(difference)

 }
 }

Solution

  • The error you're getting is pretty obscure. What it's trying to tell you is you should remove the () from the end of your timerFired in the #selector.

    var timer = NSTimer(
        timeInterval: 1.0,
        target: self,
        selector: #selector(TestTimer.timerFired),
        userInfo: nil,
        repeats: true)
    

    However, this isn't going to make your code how you want it to work – as self in the timer declaration refers to the view controller, not the timer. I would recommend you create a wrapper class for NSTimer, along with a delegate pattern in order to achieve what you want.

    You should note that the documentation states that you shouldn't attempt to subclass NSTimer, so you could do something like this instead:

    // the protocol that defines the timerDidFire callback method
    protocol TimerDelegate:class {
        func timerDidFire(cumulativeTime:NSTimeInterval)
    }
    
    // your timer wrapper class
    class TimerWrapper {
    
        // the underlying timer object
        weak private var _timer:NSTimer?
    
        // the start date of when the timer first started
        private var _startDate = NSDate()
    
        // the delegate used to implement the timerDidFire callback method
        weak var delegate:TimerDelegate?
    
        // start the timer with a given firing interval – which could be a property
        func startTimer(interval:NSTimeInterval) {
    
            // if timer already exists, make sure to stop it before starting another one
            if _timer != nil {
                stopTimer()
            }
    
            // reset start date and start new timer
            _startDate = NSDate()
            _timer = NSTimer.scheduledTimerWithTimeInterval(interval,
                                                            target: self,
                                                            selector: #selector(timerDidFire),
                                                            userInfo: nil, repeats: true)
        }
    
        // invalidate & deallocate the timer,
        // make sure to call this when you're done with the timer
        func stopTimer() {
            _timer?.invalidate()
            _timer = nil
        }
    
        // make sure to stop the timer when the wrapper gets deallocated
        deinit {
            stopTimer()
        }
    
        // called when the timer fires
        @objc func timerDidFire() {
    
            // get the change in time, from when the timer first fired to now
            let deltaTime = NSDate().timeIntervalSinceDate(_startDate)
    
            // do something with delta time
    
            // invoke the callback method
            delegate?.timerDidFire(deltaTime)
        }
    }
    

    You can then use it like this:

    // your view controller class – make sure it conforms to the TimerDelegate
    class ViewController: UIViewController, TimerDelegate {
    
        // an instance of the timer wrapper class
        let timer = TimerWrapper()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // set the timer delegate and start the timer – delegate should be set in viewDidLoad,
            // timer can be started whenever you need it to be started.
            timer.delegate = self
            timer.startTimer(1)
        }
    
        func timerDidFire(cumulativeTime: NSTimeInterval) {
    
            // do something with the total time
    
            let dateComponentsFormatter = NSDateComponentsFormatter()
            let text = dateComponentsFormatter.stringFromTimeInterval(cumulativeTime)
            label.text = text
        }
    }
    

    As far as the appropriateness of using an NSTimer here goes, as you're only using a time interval of 1 second, an NSTimer is suitable. By taking the time interval over the total timer duration, you can average out any small firing inaccuracies.