Concurrent processing on main queue during UITableView scroll - cocoa-touch

I have a stopwatch screen in my app.
There is a main label indicating passing time every 100 milliseconds (updates constantly).
Just below that label, in the same ViewController I have a UITableView.
Whenever I'm scrolling the UITableView, the stopwatch labels stops updating.
I've tried using Grand Central Dispatch as follows, but it doesn't work and I didn't expect it would, since it's still two operations running on the same queue.
dispatch_async(dispatch_get_main_queue(),^{
MyTimerObject *t = (MyTimerObject*)notification.object;
lblMainTimer.text = t.MainTimerStringValue;});
So how should I approach this problem?
This all happens within the receiveNotification method of NSNotification, which make me wonder if it's not NSNotification that locks up during the table scroll event and not the label update...

Is your stopwatch updating with NSTimer or CADisplayLink? The issue associated with failure to update while scrolling is generally an incorrect choice of run loop modes.
For example, this will pause while tableview is scrolling.
NSTimer *timer = [NSTimer timerWithTimeInterval:0.1 target:self selector:#selector(handleTimer:) userInfo:nil repeats:TRUE];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
Whereas this will not:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Bottom line, don't use NSDefaultRunLoopMode, but rather use NSRunLoopCommonModes, and you may find it continues to run even when scrolling the table view.

Related

How do I replace this NSTimer with a CADisplayLink?

I realize that a CADisplayLink would be better suited for the nature of my current project, however, i can't quite figure out how to implement a CADisplayLink and replace my NSTimer.
below is the code for my NSTimer
Movement = [NSTimer scheduledTimerWithTimeInterval:0.002 target:self selector:#selector(BarMoving) userInfo:nil repeats:YES];
how can I create a CADisplayLink that will perform the same function but more efficiently?
Create the thing:
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:#selector(BarMoving)];
Start it running:
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
... that'll cause your display link to issue calls to BarMoving on the main run loop (which is the one associated with the main thread and therefore the main queue) whenever that run loop is in the default mode. So things like when the user has their finger down scrolling a scroll view will pause your timer. NSTimer has the same default behaviour.

NStimer fire first and then on schedule

Is it possible to make a NStimer that fires first when it is initialized and then afterwards on a 0.01 seconds schedule? I have this code..
self.displayTimerTotalTime = [NSTimer timerWithTimeInterval:0.01
target:self
selector:#selector(timerFiredTotalTime:)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.displayTimerTotalTime forMode:NSRunLoopCommonModes];
Problem is that the user can stop the timer multiple times and run it again, but this creates a 0.01 delay every time that can cause problems for the user experience. It is not good enough to check for the delay later and remove it.
You can call the -fire method to fire the timer at any time. Just add this after the above code:
[self.displayTimerTotalTime fire];
You could always call the selector yourself:
[self timerFiredTotalTime:self.displayTimerTotalTime];

Does NSTimer run across entire app?

If I start an NSTimer like this:
#property (strong) NSTimer * messageTimer;
self.messageTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(checkForMessages)
userInfo:nil
repeats:YES];
Does it continue to run when I switch to different view controllers?
Until I cancel it with:
[messageTimer invalidate]; self. messageTimer = nil;
Yes.
Okay, now here is an extended description. NSTimer registers itself on nearest NSRunLoop, that is, current dispatch loop (they may nest). This loop asks various sources for events and calls corresponding callbacks.
When it is time for NSTimer to fire, it returns YES to NSRunLoop and that runs passed callback. There is no such thing as "other current view controller". It is all about first responder and view hierarchy, neither doesn't have any effect on run loops.

UiView fadeOut if not touched

I'd like to reproduce this behavior in my iPad application.
I have a subView that contains four custom buttons.
The view has an alpha value of 0.0
I have another custom button outside of the view descripted above that is always visible.
When the user touches the visible button the view appear animating its alpha to 1.0 showing the other 4 buttons.
Now I'like to start a timer that fires the view fadeOut after 2 seconds
When and if the user interact (say touchDown or whatever) with the buttons the timers need to be reset.
In other words the view may disappear only when nobody touches a button inside It.
Can you help me with this.
I've managed to learn the basics of UIView animation but I don't know how to queue them.
My iPad has iOS 3.2.2. installed.
Sorry for the bad explanation but this is my first iPad application and my first obj-c project.
You'd keep an NSTimer instance variable for that. As soon as your view is completely visible, which you can notice by e.g. implementing the fade in animation's delegate, you initialize it like this:
_fadeTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(fade:) userInfo:nil repeats:NO];
Make sure _fadeTimer is an instance variable, you need to be able to reset it. Then implement the fade out method:
- (void)fade:(NSTimer *)aTimer {
// Forget about timer!
_fadeTimer = nil;
[UIView beginAnimations:nil context:NULL];
// fade here
[UIView commitAnimations];
}
Upon every user interaction you just call a method that delays the fade. To do this, delete and re-create the timer. Or change it's fire date:
- (void)delayFade {
[_fadeTimer setFireDate: [NSDate dateWithTimeIntervalSinceNow: 2.0]];
}
PS: There is no need to explicitly retain the timer. It's retained by the runloop until it fires. After the callback, it will be released anyways. Just make sure you always reset the variable to nil, otherwise your app may crash on an invalid access. If you need to delete the time beofre it fired, call the invalidate method.

NSTimer with a menu bar app

I'm working on a simple timer app, and I've created a NSStatusItem with a menu and I have some NSTextField labels that updates the timer labels (http://cld.ly/e81dqm) but when I click on the status item the NSTimer stops (and stops updating the labels)..... how can I get around this problem?
EDIT: here's the code that starts the timer:
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerDidUpdate:) userInfo:nil repeats:YES];
You should add timer into MainRunLoop as given below:
NSRunLoop * rl = [NSRunLoop mainRunLoop];
[rl addTimer:timer forMode:NSRunLoopCommonModes];
I'm guessing the timer resumes as soon as you stop interacting with the NSStatusItem? (After the menu's dismissed & mouse button released).
The user interaction puts the main run loop into a mode where it doesn't update timers, so if your label has to continually update, you'll probably need to move the NSTimer and the label drawing to a separate process or another thread.