31

I use a NSTimer which fires every second and updates a label which displays the remaining time until an event.

I works fine so far. The problem is while I am scrolling the TableView my Label does not update, because the MainThread is blocked by the touch/scroll event.

I thought about creating a second thread for the Timer but I couldn't update the label from a background thread anyways. I had to queue it with performSelector... on the MainThread where it would stuck like before.

Is there any way to update the label while scrolling?

Alexander Theißen
  • 3,799
  • 4
  • 28
  • 35
  • For Swift, you can use [Timer#scheduledTimer(timeInterval:target:selector:userInfo:repeats:)](https://developer.apple.com/documentation/foundation/timer/1412416-scheduledtimer) as it "_Creates a timer and schedules it on the current run loop in the default mode_". – toraritte May 24 '18 at 17:21

3 Answers3

46

The problem is that a scheduledTimer will not get called while the main thread is tracking touches. You need to schedule the timer in the main run loop.

So instead of doing

[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateLabel:) userInfo:nil repeats:YES];

use

NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
DHamrick
  • 8,338
  • 9
  • 45
  • 62
  • I thought scheduledTimerWithTimeInterval adds it to the current run loop which IS the mainRunLoop because my application is single threaded. – Alexander Theißen Jun 03 '11 at 17:08
  • NSRunLoopCommonModes will make your timer still get called even while the table view is tracking your touches. – DHamrick Jun 03 '11 at 17:48
  • @Alexander: It adds it in the *default* run loop mode, not all the modes considered *common* run loop modes. During touch tracking, the run loop is being run in some mode other than the default, so your timer does not get checked. – Jeremy W. Sherman Jun 03 '11 at 18:18
  • Typo on `NSRunLoopCommonMods`? Should be `NSRunLoopCommonModes` – Gustav Aug 21 '12 at 09:16
  • Thx, Here is converted for rubymotion usage `timer = NSTimer.timerWithTimeInterval(1.0,target:self,selector:'updateLabel',userInfo:nil,repeats:true) NSRunLoop.mainRunLoop.addTimer(timer,forMode:NSRunLoopCommonModes)` – Bachet Sep 29 '13 at 23:23
  • Did you copied ax123man answer, made it fancy and posted as your own? WTF dude. not cool. – GeneCode Aug 24 '16 at 09:33
0

try this:

    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updateClock) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
ax123man
  • 578
  • 5
  • 17
-3

You could also use GCD. So run

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
    [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateLabel:) userInfo:nil repeats:YES];
});

and now in your updateLabel method

- (void) updateLabel:(id) sender {
    NSString *text = @"some text";
    dispatch_sync(dispatch_get_main_queue(), ^{
        label.text = text;
    });
}

This will update the label in the main thread.

brandontreb
  • 397
  • 2
  • 12
  • That is a recipe for disaster. The timer is registered with the runloop of whatever random (and likely temporary) thread the high-priority concurrent queue decides to use, which means it might never get a chance to fire. If you want to use a delay with GCD, use `dispatch_after`. In this case, you would set it up to dispatch the block after 1 second to the main queue. – Jeremy W. Sherman Jun 03 '11 at 18:21
  • hrmm... yeah I guess that makes sense. In that case, what would the dispatch code look like? – brandontreb Jun 03 '11 at 18:25
  • See for example https://gist.github.com/1006912. Comments are a bad place to write code. – Jeremy W. Sherman Jun 03 '11 at 18:44
  • This is the wrong approach to this question. I developed the same and got stuck. – Poliquin May 07 '14 at 15:16