38

I have a scrollView with an imageView inside of it. The scrollView is a subView of the superView, and the imageView is a subView of the scrollView. I also have a label (at the super-view level) that receives updated values on its text property from a NSTimer every millisecond.

The problem is: During scrolling, the label stops to display the updates. When the scrolling is end, updates on the label restart. When updates restart they are correct; this means that label.text values are updated as expected, but while scrolling, updates display is overriden somewhere. I would like to display updates on the label regardless of scrolling or not.

Here is how the label updates are implemented:

- (void)startElapsedTimeTimer {

     [self setStartTime:CFAbsoluteTimeGetCurrent()];
     NSTimer *elapsedTimeTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(updateElapsedTimeLabel) repeats:YES];
}

- (void)updateElapsedTimeLabel {

    CFTimeInterval currentTime = CFAbsoluteTimeGetCurrent();
    float theTime = currentTime - startTime;

    elapsedTimeLabel.text = [NSString stringWithFormat:@"%1.2f sec.", theTime];
}

Thanks for any help.

Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
brainondev
  • 1,117
  • 1
  • 11
  • 20

6 Answers6

79

I had recently the same trouble and found the solution here: My custom UI elements....

In short: while your UIScrollView is scrolling, the NSTimer is not updated because the run loops run in a different mode (NSRunLoopCommonModes, mode used for tracking events).

The solution is adding your timer to the NSRunLoopModes just after creation:

NSTimer *elapsedTimeTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 
                                                             target:self 
                                                           selector:@selector(updateElapsedTimeLabel) 
                                                           userInfo:nil 
                                                            repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:elapsedTimeTimer 
                             forMode:NSRunLoopCommonModes];

(The code comes from the post linked above).

Community
  • 1
  • 1
sergio
  • 68,819
  • 11
  • 102
  • 123
  • Thanks for the easy solution. – FreeAsInBeer Apr 23 '12 at 13:58
  • 1
    Is there a reason why you use scheduledTimerWithTimeInterval:target:selector:userInfo:repeats while he uses timerWithTimeInterval:target:selector:userInfo:repeats ? – Kyle Robson Jul 18 '12 at 15:41
  • 2
    I used the same call as the OP. It would be more correct to use `timerWithTimeInterval:target:selector:userInfo:repeats` because we are going to add the timer to the run loop anyway, so don't need the `scheduled` version of the call. – sergio Jul 18 '12 at 16:33
  • 2
    This is the reason my NSURLConnection delegate methods weren't being called while scrolling; I needed to use NSURLConnection's `scheduleInRunLoop:forMode:` method. So +1 for pointing me in the right direction. – davidf2281 May 07 '14 at 18:40
15

sergio's solution in Swift 5:

timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in
    self?.updateTimeLabel()
}
RunLoop.current.add(timer, forMode: .common)
protspace
  • 2,047
  • 1
  • 25
  • 30
4

sergio's solution in Swift 2:

self.updateTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "updateFunction", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.updateTimer, forMode: NSRunLoopCommonModes)
Rob
  • 26,989
  • 16
  • 82
  • 98
rmooney
  • 6,123
  • 3
  • 29
  • 29
4

I have seen similar behavior in combination with scrolling a UIScrollView. What probably happens is that the scrolling action completely blocks the main run loop, which is responsible for anything related to view updates. You are not doing anything wrong here, updates to the view hierarchy should be handled by the main loop so you can't just put your UILabel updating on a background thread (although I'd probably still try it to see what happens).

I haven't really done research into this issue, but I assume there is little you can do about it. I'll happily accept answers that prove me wrong!

Pascal
  • 16,846
  • 4
  • 60
  • 69
  • Thanks Pascal. I never tried to use different threads. I will read something and I'll give it a try. – brainondev Mar 21 '11 at 13:51
  • At the moment I've tried to pass updated values to an instance class variable. Using scrollViewDidScroll delegate method to update the label (during scrolling action) does not solve this problem. "Sob!" – brainondev Mar 21 '11 at 14:21
  • Yeah, I feared that'd be the case. Scrolling and the fluidity of scrolling is vital to the iPhone experience, from a user perspective it makes sense that all the power is used to animated that, that might at least be an explanation. – Pascal Mar 21 '11 at 15:11
  • Anyway... we can use any task related to the scrollView's contentOffset property. I think iOS should look at supporting simoultaneous tasks that are not restricted to UIScrollViewDelegate protocol. – brainondev Mar 21 '11 at 15:30
  • Read this for run loops and its modes http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html – SPatil Nov 01 '11 at 14:55
  • RunLoop of a thread can be in different modes, including `default` and `tracking` modes. Scrolling a `UIScrollView` doesn't block anything - it just switches the main thread's RunLoop into the tracking mode. That's why if you want some code to be executed in any RunLoop mode then you can add your task for `common` modes (which includes both `default` and `tracking`). – Legonaftik Sep 29 '21 at 16:02
3

sergio's solution in Swift 3.x:

self.updateTimer = Timer.scheduledTimer(timeInterval:1.0, target: self, selector: "updateFunction", userInfo: nil, repeats: true)
RunLoop.current.add(self.updateTimer, forMode: RunLoopMode.commonModes)
Rob
  • 26,989
  • 16
  • 82
  • 98
yo2bh
  • 1,356
  • 1
  • 14
  • 26
0

sergio's solution in Swift 3.x:

timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
RunLoop.current.add(self.timer, forMode: .common)
msmq
  • 1,298
  • 16
  • 28