I have coded a timer using an NSTimer that updates a label. The problem is that in the same viewcontroller I have a uitableview and when I scroll it down, the timer doesn't update its value so the user can "cheat" to stop the timer.
I think this could be easy to fix with a serial queue with CGD but I don't figure out how to do it.
Thanks in advance,
Juan
First of all keep in mind that you cannot perform UI changes in any other thread than the main thread. Having said that, you need the NSTimer to fire in the main queue, otherwise the program will crash when changing the UILabel. Have a look at this links http://bynomial.com/blog/?p=67 and this one http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class/Reference/Reference.html
To my knowledge, if you schedule the timer in the for the NSRunLoopCommonModes it will ignore event updates and fire the timer jsut how you want it:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerDidTick:)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-(void) timerDidTick:(NSTimer*) theTimer{
[[self myLabel] setText:#"Timer ticked!"];
}
I have faced same problem if u run timer in main thread than when you scroll tableview its stop timer too. Solution is that you run timer in background and update GUI in main thread
UIApplication *app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//run function methodRunAfterBackground
updateTimer1=[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(updateGUIAudioPlayer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:updateTimer1 forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
-(void)updateGUIAudioPlayer {
dispatch_async(dispatch_get_main_queue(), ^{
self.label.text = #"your text";
});
//but if you want to stop timer by himself than use main thread too like that
dispatch_async(dispatch_get_main_queue(), ^{
[updateTimer1 invalidate];
});}
-(void) timerDidTick:(NSTimer*) theTimer{
_currentNumber =_currentNumber+1;
//check if current number is bigger than the contents of the word array
if (_currentNumber <=wordArray.count-1) {
NSLog(#"_currentNumber #%i wordArray #%i",_currentNumber,wordArray.count);
_RandomLoadingText.text = #"Timer" ;
[self changeTextInRandomLoadingLabel:_currentNumber];
}
else if (_currentNumber >=wordArray.count-1)
{
NSLog(#"reached end of array");
[timer invalidate];
}
}
-(void)changeTextInRandomLoadingLabel:(int)myInt
{
_RandomLoadingText.text = [wordArray objectAtIndex:myInt];
}
//---------
view did load
_RandomLoadingText.text = #"Test";
_currentNumber =0;
timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerDidTick:)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
To update the UI you need to use OperationQueue.main. Supposing you have a UILabelnamed timeLabel:
var timeInSeconds = 0
timer = Timer(timeInterval: 1, repeats: true, block: { timer in
timeInSeconds += 1
OperationQueue.main.addOperation {
self.timeLabel.text = String(timeInSeconds)
}
})
RunLoop.main.add(timer, forMode: .commonModes)
Related
I have strange issue with timer. timer is updating well within application. i am showing the code.
// Start Timer
#pragma mark- Timer
- (void)startTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateTimer:) userInfo:nil repeats:YES];
}
// Update Timer
- (void)updateTimer:(NSTimer *)theTimer
{
NSLog(#"called");
}
Problem: I have a textview when i scroll text within the
textview the updateTimer method stops calling and when I stop
scrolling then it starts to update timer.
Now what to do to continue calling the update timer method?
For do fix this issue you need to add your NSTimer to the mainRunLoop. Such like,
- (void)startTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateTimer:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
You can do this way also,
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:#selector(updateTimer:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSRunLoopCommonModes];
I have a static method that creates a NSTimer and runs it in the background thread, like so:
+ (void) callInBackgroundThread {
NSTimer *timer = [NSTimer timerWithTimeInterval:0.2
target:self
selector:#selector(callToMainThread)
userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
and then i call the main thread upon completion like so:
+ (void) callToMainThread{
NSTimer *timer = [NSTimer timerWithTimeInterval:0
target:self
selector:#selector(foo1)
userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
While this works i feel that this is quite sketchy and I wonder if there is a better way of doing this.
I would appreciate suggestions, please note that the methods where are static.
Any help would be appreciated.
Regards
performSelectorOnMainThread:withObject:waitUntilDone: also works for classes!
+ (void) callToMainThread {
[self performSelectorOnMainThread:#selector(foo1) withObject:nil waitUntilDone:NO];
}
I use an NSTimer to move a UIImageView every 1 second, I want to decrease the time every 2 seconds from 1 to 0.5 as result the image will move faster inside the view. Any ideas will be very help full.
This is the timer that i use for moving the UIImageView
float obstaclesSpeed = 1.0;
movementTimer = [NSTimer scheduledTimerWithTimeInterval:obstaclesSpeed target:self selector:#selector(updateSpeed) userInfo:nil repeats:YES];
This is my code
-(void)updateSpeed {
obstaclesSpeed = obstaclesSpeed / 2;
[self startMoveObstacles];
}
I believe something I do wrong
Are you wanting to move the image every 2 seconds, or make it move increasingly faster? It won't move faster, if you only move it every 2 seconds.
You can read the docs for NSTimer, but to take some of the headache out of it, here's some examples of how to use NSTimer for this.
If you're wanting to move it every two seconds, you can do the following.
- (void) startTimer {
[_myTimer invalidate];
_myTimer = nil;
_myTimer = [NSTimer timerWithTimeInterval:2. target:self selector:#selector(respondToTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes];
}
- (void)respondToTimer:(NSTimer*)timer {
//Code to move your UIImageView
}
If you're wanting to move it increasingly faster, you can do the following.
- (void) startTimer {
[_myTimer invalidate];
_myTimer = nil;
_myTimer = [NSTimer timerWithTimeInterval:_timeInterval target:self selector:#selector(respondToTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes];
}
- (void)respondToTimer:(NSTimer*)timer {
//Code to move your UIImageView
_numberOfMoves++;
if (_numberOfMoves < MAX_MOVES) {
_timeInterval /= 2;
[self startTimer];
}
}
I have a NSTimer defined as follows:
timer = [NSTimer scheduledTimerWithTimeInterval:30
target:self
selector:#selector(fooBar)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
I want it to call the call back function fooBar in this case using a background thread. But when I check with if ([NSThread mainThread]) i'm always getting it on the main thread. Is there any other way aside from dispatching a thread from the callback function?
You are adding the timer to main thread. Your call back will also be in main thread. To schedule the timer in a background thread, I think you need to use NSOperation subclass and schedule the timer to [NSRunLoop currentRunLoop] from inside the operation's main method.
#import <Foundation/Foundation.h>
#interface BackgroundTimer : NSOperation
{
BOOL _done;
}
#end
#import "BackgroundTimer.h"
#implementation BackgroundTimer
-(void) main
{
if ([self isCancelled])
{
return;
}
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:30
target:self
selector:#selector(fooBar)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//keep the runloop going as long as needed
while (!_done && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]);
}
#end
If you want to run a timer on a background thread, the most efficient way to do that is with a dispatch timer:
#property (nonatomic, strong) dispatch_source_t timer;
and you can then configure this timer to fire every two seconds:
- (void)startTimer {
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
// call whatever you want here
});
dispatch_resume(self.timer);
}
- (void)stopTimer {
dispatch_cancel(self.timer);
self.timer = nil;
}
I'm implementing a countdown with its value displayed on a UILabel, but have run into a problem. Here's the simplified code:
self.countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(countdown) userInfo:nil repeats:YES];
- (void)countdown {
self.countdownLabel.text = [NSString stringWithFormat:#"%i",[self.countdownLabel.text intValue]-1];
// Handle time out
if ([self.countdownLabel.text intValue] == 0) {
[self.countdownTimer invalidate];
self.countdownTimer = nil;
}
}
It works fine, but if I do other UI operations in the viewcontroller, like scroll a scrollview, the timer hangs as the scrollview scrolls and then boosts up to make up for the idle moments.
I tried dispatching the updating of the label to a background queue which of course didn't work.
dispatch_queue_t bgQ = dispatch_queue_create("bgQ", 0);
dispatch_async(bgQ, ^{
self.countdownLabel.text = [NSString stringWithFormat:#"%i",[self.countdownLabel.text intValue]-1];
});
What would be the solution here?
self.countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(countdown) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.countdownTimer forMode:NSRunLoopCommonModes];
Remember that in your countdown method you need an escape to invalidate your countdownTimer.
The timer will start without fire instruction when the
[[NSRunLoop mainRunLoop] addTimer:self.countdownTimer forMode:NSRunLoopCommonModes];
line is executed.
ABSOLUTELY no dispatch async for UI changes.
Hope this helps.
Swift 3.0 syntax
var timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ViewController.updateTimer), userInfo: nil, repeats: true);
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)