19

I have a spinning circle UI element that is updated by an NSTimer. I also have a UIScrollView that would 'block' the spinning circle while being scrolled. That was expected, because the timer and the scroll view where both in the same thread.

So I put the timer in a separate thread which basically works great! I can see it working because NSLogs keep coming even while scrolling.

But my problem is that my spinning circle is still stopping on scrolling! I suspect redrawing is halted until the next iteration of the main thread's run loop (?). So even if its angle is changed all the time, it might not be redrawn...

Any ideas what I can do? Thanks!

Sebastian
  • 904
  • 1
  • 8
  • 16

3 Answers3

45

While scrolling, the main thread's run loop is in UITrackingRunLoopMode. So what you need to do is schedule your timer in that mode (possibly in addition to the default mode). I believe NSRunLoopCommonModes includes both the default and event tracking modes, so you can just do this:

NSTimer *timer = [NSTimer timerWithTimeInterval:0.42 target:foo selector:@selector(doSomething) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

By the way, you should do this all on the main thread. No need to spawn a background thread (unless each timer invocation is going to do some lengthy processing).

Daniel Dickison
  • 21,832
  • 13
  • 69
  • 89
  • You're right, NSRunLoopCommonModes is the correct mode for this, thanks! I got rid of the background thread and the timer fires now, even during scrolling. But my problem is still there, no redrawing happens. – Sebastian Nov 09 '10 at 19:04
  • What are you doing to trigger redrawing? I would think a `setNeedsDisplay` will do the trick. Maybe you should post your timer method. – Daniel Dickison Nov 09 '10 at 21:10
  • I'm using UIView's setNeedsDisplay/display mechanism. The docs say: 'Any UIView objects marked as needing display are automatically redisplayed when the application returns to the run loop.' So I guess that's why no redrawing happens... – Sebastian Nov 09 '10 at 22:27
  • Interesting -- I would've thought `setNeedsDisplay` will still lead to a `display` and `drawRect` call even if the run loop is trapped in `UITrackingRunLoopMode`, but perhaps that's not true. I know for a fact that setting the `image` property of a `UIImageView` will be updated in such situations, but haven't tried any custom drawing. You could try calling `display` directly in your timer method at least to see if this is in fact what's happening. – Daniel Dickison Nov 10 '10 at 16:29
  • Sorry, that was my mistake! UIViews GET updated no matter what mode the run loop is in! It turned out the bug was in the code that send new angle values to my spinning circle view. And ironically, that code also had a NSTimer which was attached in default mode only :-). – Sebastian Nov 11 '10 at 11:06
  • Whew -- that sounds the way it should be. Please accept the answer if this worked out for you so it can help others with the same issue. – Daniel Dickison Nov 11 '10 at 14:47
  • Hey that helped! Could you also explain what are runloops? – Burhanuddin Sunelwala May 06 '13 at 13:06
  • If you're using animation blocks and not timers then you are in a bit of a pickle. I had to turn off scrolling before triggering animation – Daniel Galasko Jul 15 '15 at 12:24
3

UIScrollView doesn't just block NSTimers, it blocks the entire main thread. And UIKit objects should be accessed on the main thread only (and, often, are limited in unpredictable ways if you try to dodge round that restriction), which is probably why — even if you have a clock coming in from an external thread — you're unable to post updates.

It's a bit of a dodge, but is there any way your animation can be achieved with CoreAnimation? Things tied to that continue working irrespective of what's happening in the main thread.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Well, the timer's not blocked at all. I have it running on the main thread in NSRunLoopCommonModes now and it fires even during scrolling. But my view is not getting redrawn, at least not thru UIView's setNeedsDisplay/display mechanism. The docs say: 'Any UIView objects marked as needing display are automatically redisplayed when the application returns to the run loop.' – Sebastian Nov 09 '10 at 19:13
  • CA is a great idea, I will try that! – Sebastian Nov 09 '10 at 19:14
0

You are correct that UIScrollView does not update its content view during scrolling.

Probably your best bet -- and this is something of a hack -- is to display your animating view separately (perhaps within a frame-view, to get the free clipping) and use the scroll-view delegate callbacks to find out the current position of the scroll and position your animation accordingly.

NOTE: this suggestion is a hack! It's not best practices and it's certainly counter to simple, maintainable code without side effects. However, it's the only way I can think to get the display you want.

Another tack: re-think the importance of displaying animated contents of a scrolling view while it is scrolling.

Olie
  • 24,597
  • 18
  • 99
  • 131
  • No, the animated spinning circle is NOT part of the scroll view's content! Sorry, maybe I wasn't clear on that. – Sebastian Nov 09 '10 at 19:08