0

I'm trying to add timers to a NSRunLoop. My expected outcome is that once the timers have been added to the loop, they start counting down independent from one another.

My code now looks like this:

    var timer = NSTimer()
    let mainRunLoop:NSRunLoop = NSRunLoop()

    func blurViewActive(gestureRecognizer:UIGestureRecognizer) {
        if (gestureRecognizer.state == UIGestureRecognizerState.Began){
            println("STATE BEGAN")
            var point = gestureRecognizer.locationInView(self.tv)
            if let indexPath = self.tv.indexPathForRowAtPoint(point){
                let data = messageList[indexPath.row] as Messages
                if let theCell = self.tv.cellForRowAtIndexPath(indexPath) as? TableViewCell{

                    self.timer = NSTimer(timeInterval: 1, target: self, selector: "updateCounter", userInfo: nil, repeats: true)

                    self.mainRunLoop.addTimer(timer, forMode: NSRunLoopCommonModes)
                    mainRunLoop.run()
                }
        }
}
}
    var counter = 10
    func updateCounter(){
        if counter == 0{
        timer.invalidate()
        }else{
        counter = --counter
        println(counter)
        }
    }

Right now, nothing seems to happen when my button is pressed. My understanding is that once the timer has been added to the run-loop, it will start running independently.

Any suggestions on how this is done correctly would be appreciated.

martin
  • 1,894
  • 4
  • 37
  • 69
  • I strongly assume that `mainRunLoop.run()` blocks the main thread. Is there any special reason that you define your own runloop, instead of just using `NSTimer(scheduledTimerWithTimeInterval: ...)`, which adds the timer to the main runloop automatically? – Martin R Jan 19 '15 at 12:16
  • @MartinR You mean just replacing the self.timer... with "self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateCounter", userInfo: nil, repeats: true)"? – martin Jan 19 '15 at 12:24
  • That's exactly what I meant. – Martin R Jan 19 '15 at 12:25
  • @MartinR When I do that, the timer gets accelerated. The selector gets activated at every tick from both timers. – martin Jan 19 '15 at 12:33

2 Answers2

1

There are two problems in your code.

First of all, mainRunLoop.run() blocks the main thread. Using your own runloop is a bit tricky, but actually not necessary here. You can create a timer running on the main runloop with

timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateCounter", userInfo: nil, repeats: true)

and remove your mainRunLoop completely.

The other problem is that you use a single timer variable and counter. If you want independent timers for each table view row then you need multiple independent timer and counter variables.

One possible solution is to use two dictionaries

var timerDict : [ NSIndexPath : NSTimer ] = [:]
var counterDict : [ NSIndexPath : Int ] = [:]

which store the timer and the current counter for each active count-down, using the index path as the key.

On a long press, you would check the first dictionary if a timer is already active for this row, and if not, create and start a new one:

    if timerDict[indexPath] == nil {
        // No timer running for this row, start a new one:
        counterDict[indexPath] = 10
        timerDict[indexPath] = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateCounter:",
             userInfo: indexPath, repeats: true)
    }

Note that the index path is passed as userInfo: argument to the timer. The callback method can then retrieve the index path from the passed timer parameter and act accordingly:

func updateCounter(timer : NSTimer) {
    if let indexPath = timer.userInfo as? NSIndexPath {
        if var counter = counterDict[indexPath] {
            if counter == 0 {
                // stop timer and remove from dictionaries:
                timer.invalidate()
                timerDict.removeValueForKey(indexPath)
                counterDict.removeValueForKey(indexPath)
                println("indexPath: \(indexPath) DONE")
           } else {
                // decrement counter and update dictionary:
                --counter
                println("indexPath: \(indexPath) counter: \(counter)")
                counterDict[indexPath] = counter
            }
        }
    }
}

Note also (as @gnasher729 said in his answer), the correct type for a timer callback is

func updateCounter(timer : NSTimer) { ... }

with the corresponding selector "updateCounter:" with a trailing colon.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks for good suggestions, however, this does not solve the issue which was running multiple timers/countdowns at the same time. – martin Jan 19 '15 at 13:31
  • @frank21: I have tested the above code, and there is always only one timer running, as guaranteed by the check `if timer == nil { ... }`. – Martin R Jan 19 '15 at 13:41
  • Yes, but I think I didn't make myself clear, or that I am misunderstanding the problem. I want to start _multiple_ countdowns. So that when I execute my long press, and one timer is already active, I want to start an additional countdown. – martin Jan 19 '15 at 13:45
  • @frank21: OK, that wasn't clear to me, I will update the answer. – But what should happen if a second long press is done on the *same row*, while the previous timer is still running? Two count-down timers for one row? Or stop the first and start new one? Or ignore the second and continue the first one? – Martin R Jan 19 '15 at 14:00
  • Then the timer that's already running should continue to run, without being interrupted in any way. – martin Jan 19 '15 at 14:04
-1

Timer methods always have a parameter, which is the timer object itself. So "updateCounter" as a selector is probably wrong.

gnasher729
  • 51,477
  • 5
  • 75
  • 98