NSTimer Repeats not working for me - objective-c

Problem, runs once.
-(void)firingLogicForPlayer:(Player *)player {
if (player.playerTargetLock) {
if (!_fireRateTimer) {
_fireCounter = 0;
_fireRateTimer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(burstControl:)
userInfo:player.name
repeats:YES];
[_fireRateTimer fire];
BOOL timerState = [_fireRateTimer isValid];
NSLog(#"Timer validity is: %#", timerState?#"YES":#"NO");
}
}
}
-(void)burstControl:(NSTimer *)theTimer {
NSLog(#"burstControl Initiated");
NSString *playerName = (NSString *)[theTimer userInfo];
Player *player = (Player *)[self childNodeWithName:playerName];
if (_fireCounter < 5) {
[self playerBeginFiring:player];
_fireCounter++;
} else {
NSLog(#"this ran to kill timer");
[_fireRateTimer invalidate];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self firingLogicForPlayer:player];
});
}
}
2015-09-16 13:37:44.964 ***[20592:3367845] burstControl Initiated
2015-09-16 13:37:44.974 ***[20592:3367845] Timer validity is: YES
2015-09-16 13:37:45.147 ***[20592:3367845] hit made
This is what logs, how the logic works is, firingLogic is initialised on a target lock. So the timer should run 5 times before being invalidated because of the _fireCounter counter. timer begins burst control, checks firecounter if firecounter is < 5 it fires a bullet, increases the firecounter. if the firecounter > 5 it invalidates the timer, and dispatches it to run again after 1.5 seconds.
However, the problem is that the timer is only running once. Yet, it's valid after the initial fire. Pretty confused.

You have to add it to the NSRunLoop. Otherwise you can use + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

Please check below code
if (!_fireRateTimer) {
_fireCounter = 0;
_fireRateTimer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(burstControl:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_fireRateTimer forMode:NSDefaultRunLoopMode];
BOOL timerState = [_fireRateTimer isValid];
NSLog(#"Timer validity is: %#", timerState?#"YES":#"NO");
}
Thanks :)

Related

Calling a method every second doesn't work

I am trying to schedule a GNUStep Objective-C method call to run every second for a variable number of seconds. I am trying to use NSTimer to schedule the method call, but the handler method never gets called.
Here is my code:
Timer.m:
- (id) init {
self = [super init];
if(self) {
_ticks = 0;
}
return self;
}
- (void) startWithTicks: (unsigned int) ticks {
_ticks = ticks; //_ticks is an unsigned int instance variable
if(_ticks > 0) {
[NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: #selector(onTick:)
userInfo: nil
repeats: YES];
}
}
- (void) onTick: (NSTimer*) timer {
NSLog(#"tick");
_ticks--;
if(_ticks == 0) {
[timer invalidate];
timer = nil;
}
}
main.m:
int main(int argc, const char* argv[]) {
Timer* t = [[Timer alloc] init];
NSLog(#"Setting timer");
[t startWithTicks: 3];
usleep(5000);
NSLog(#"End of timer");
return 0;
}
I would expect the output to be
Setting timer
tick
tick
tick
End of timer
However, the output is
Setting timer
End of timer
Why is this and how can I fix it?
The timer won't run while your thread is sleeping.
Your timer class code works fine if you're using it from a ViewController.
If instead you'd like to use it within the main method, you'll want to explicitly run the mainRunLoop. Try adjusting your main method to this:
int main(int argc, char * argv[]) {
Timer *timer = [[Timer alloc] init];
NSLog(#"Setting Timer");
[timer startWithTicks:3];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(#"End of Timer");
return 0;
}
to run the mainRunLoop running for 3 seconds, which should produce your desired output.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
https://developer.apple.com/documentation/foundation/nsrunloop

showing Count down timer in a label objective c

I am new to IOS.I want to user countdown timer.I mean If it set timer for 5 min then after 5 min a function should call so I am using NSTimer for that and I will show min and seconds in a label so after 5 min the label will start from 0 min so it is perfectly working when my app is in foreground when when my app go to background NSTimer will not work after 3 min so when ever I am going I am background I am save time and second in NSUserDefault and I am assigning that values to label when it comes to foreground but the timer is not showing perfectly
-(void) StartTimer
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)timerTick:(NSTimer *)timer
{
timeSec++;
if (timeSec >= 60)
{
timeSec = timeSec-60;
timeMin++;
}
// else
// {
// [self StopTimer];
// }
//Format the string 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
//[timeLabel setStringValue:timeNow];
self.lblForTimer.text= timeNow;
NSNumber *numberValue = [NSNumber numberWithInt:[user.timerInterval intValue]];
if (timeMin>= [numberValue intValue])
{
timeMin = 0;
timeSec++;
}
}
//Call this to stop the timer event(could use as a 'Pause' or 'Reset')
- (void) StopTimer
{
[timer invalidate];
timer=nil;
timeSec = 0;
timeMin = 0;
//Since we reset here, and timerTick won't update your label again, we need to refresh it again.
//Format the string in 00:00
NSString* timeNow = [NSString stringWithFormat:#"%02d:%02d", timeMin, timeSec];
//Display on your label
// [timeLabel setStringValue:timeNow];
self.lblForTimer.text= timeNow;
}
- (void)appWillEnterForeground:(NSNotification *)notification {
NSLog(#"will enter foreground notification");
}
- (void)applicationDidEnterBackground:(NSNotification *)notification {
NSLog(#"did enter backgroun notification");
NSLog(#"%d,%d",timeMin,timeSec);
NSDate *currentDate= [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:currentDate forKey:#"backgroundDate"];
[[NSUserDefaults standardUserDefaults] setInteger:timeSec forKey:#"sec"];
[[NSUserDefaults standardUserDefaults] setInteger:timeMin forKey:#"min"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self StopTimer];
}
- (void)appDidBecomeActive:(NSNotification *)notification {
NSDate *dateWhenAppGoesBg= (NSDate *)[[NSUserDefaults standardUserDefaults] objectForKey:#"backgroundDate"];
NSTimeInterval timeSpentInBackground = [[NSDate date] timeIntervalSinceDate:dateWhenAppGoesBg];
int timeMinBack=floor(timeSpentInBackground/60);
int timeSecBack=round(timeSpentInBackground - timeMinBack * 60);
NSLog(#"%d%d",timeMinBack,timeSecBack);
// timeMin=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"min"]+timeMinBack;
timeMin=[user.counterValue intValue];
// timeSec=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"sec"]+timeSecBack;
if (timeMin>0) {
timeSec=timeSecBack;
}
else
{
timeSec=(int)[[NSUserDefaults standardUserDefaults] integerForKey:#"sec"]+timeSecBack;
}
NSLog(#"timme sec %d",timeSec);
[self StartTimer];
}
this is my code for implementing countdown timer its perfectly working in foreground but when it comes to forground from background timer values is nit perfect there is 1 min of gap between timer cananyone help me out in this issue
You can save currentDate as property. Don't need to use NSUserDefaults in this case.
Save and manipulate your timeout just in seconds and when needed retranslate as so:
NSInteger min = self.timeout / 60;
NSInteger sec = self.timeout % 60;

Can't access NSTimer's userInfo

I have this code.
- (void)scheduleTimerAfterDelay:(NSTimeInterval)delay {
dispatch_async(dispatch_get_main_queue(), ^{
_timer = [NSTimer scheduledTimerWithTimeInterval:delay
target:self
selector:#selector(triggerTimer:)
userInfo:[NSString stringWithFormat:#"%f", delay]
repeats:NO];
});
}
- (void)triggerTimer:(NSTimer *)timer {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Triggered timer after %# s.", _timer.userInfo); // <-- Exception thrown!
// Do stuff
});
}
But when the timer triggers, _timer.userInfo causes a Exception: EXC_BAD_ACCESS (code=1, address=0xc)).
What have I missed here? Printing _timer at a breakpoint on the line of the exception shows that _timer is <__NSCFTimer: 0x14ec8cb0>. But I can't access userInfo via lldb either. I'm using ARC.
The userInfo should be a dictionary:
_timer = [NSTimer scheduledTimerWithTimeInterval:delay
target:self
selector:#selector(triggerTimer:)
userInfo:#{ #"name" : #"Zinedine Zidane",
#"age" : #42 }
repeats:NO];
and you obviously need to change the way you access it in the selector:
You need to retain the userInfo before calling dispatch_async():
- (void)triggerTimer:(NSTimer *)timer {
NSString *s = timer.userInfo; // Strong reference!
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Triggered timer after %# s.", s);
// Do stuff
});
}

NSMutableDictionary with NSTimers

There are several recipes in my CoreData. Every recipe has attributes prepHour and prepMin. I would like by tapping to button Start Timer start countdown timer. But there can be several timers (for example for each recipe) which must work simultaneously
I selected this way (may be it's wrong):
For timers I used singleton Timers.
By tapping to my Start Timer I call method with parameters (name, prepHour, prepMin)
My startTimer method creates timer and puts it to NSMutableDictionary
Calling method:
[[Timers sharedTimers] startTimer:self.recipe.name startTimer:self.recipe.prepHour startTimer:self.recipe.prepMin];
In Timers singleton:
- (void)startTimer:(NSString *)timerName startTimer:(NSNumber *)hVal startTimer:(NSNumber *)mVal
{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:60.0f target:self selector:#selector(tick:) userInfo:nil repeats:YES];
if ([timer isValid]) {
NSArray *array = [NSArray arrayWithObjects: timer, hVal, mVal, nil];
if (_timerDict == NULL) {
_timerDict = [[NSMutableDictionary alloc] init];
}
[_timerDict setObject:array forKey:timerName];
NSLog(#"%#", _timerDict);
}
}
- (void)stopTimer:(NSString *)timerName
{
NSArray *array = [_timerDict objectForKey:timerName];
NSTimer *timer = [array objectAtIndex:0];
if ([timer isValid]) {
[timer invalidate];
[_timerDict removeObjectForKey:timerName];
}
}
- (void)tick:(NSString *)timerName
{
NSArray *array = [_timerDict objectForKey:timerName];
//NSTimer *timer = [array objectAtIndex:0];
NSInteger hVal = [[array objectAtIndex:1] integerValue];
NSInteger mVal = [[array objectAtIndex:2] integerValue];
NSInteger sVal = 0;
How should I use tick method for each timer? I would like each timer call this method with own parameters (timer, prepHour, prepMin)
This may be a matter of taste, but I don't use timers to remember an expiration time, I use them to pulse at a frequency. For your app, I'd use one timer at the lowest common denominator frequency, like one second. Instead of a singleton, I have a single timer, that the app delegate can invalidate and restart.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(tick:) userInfo:nil repeats:YES];
Then keep each expiration as one property on each recipe. For example:
- (IBAction)pressedStartButtonForRecipeA:(id)sender {
// this is a NSDate * property
self.recipeADone = [NSDate dateWithTimeIntervalSinceNow:90*60]; // ninety minutes
}
- (void)tick:(NSTimer *)timer {
NSDate *now = [NSDate date];
NSTimeInterval secondsUntilAIsDone = [self.recipeADone timeIntervalSinceDate:now];
if (secondsUntilAIsDone <= 0.0) {
// update UI to say that A is done
} else {
// update UI to reduce countdown
}
// and so on for all recipes
}
This sounds like a poor use of a singleton. I would have…well, one timer per timer if you want to go with this design. Alternatively, you could have one timer and just calculate the offset from each timer's start when it ticks. (Which approach makes sense depends on your particular app. I'd usually go with the second, but YMMV.)
Also, timers do not pass an NSString to their callback; they pass themselves. To allow the callback to get some information from the timer, you can set the info its the timer's userInfo dictionary.

How to create Countdown with 10th of a second?

HI I have already created a countdown in second, lets say 10 second, but I want to make it more precise
to 10.0 and display it on a label, how do it do that? Thanks in advance
This is what I have now for the "second" countdown
my NSTimer
counterSecond = 10
NSTimer timer1 = [NSTimer scheduledTimerWithTimeInterval : 1
Target:self selector:#selector (countLabel) userInfo:nil repeats:YES];
-(void)countLabel:
counterSecond --;
self.timerLabel.text = [NSString stringWithFormat #"%d", counterSecond];
I would use a start date/time to keep track of the countdown. Because iOS can delay firing the timer for other tasks.
- (void)countdownUpdateMethod:(NSTimer*)theTimer {
// code is written so one can see everything that is happening
// I am sure, some people would combine a few of the lines together
NSDate *currentDate = [NSDate date];
NSTimeInterval elaspedTime = [currentDate timeIntervalSinceDate:startTime];
NSTimeInterval difference = countdownSeconds - elaspedTime;
if (difference <= 0) {
[theTimer invalidate]; // kill the timer
[startTime release]; // release the start time we don't need it anymore
difference = 0; // set to zero just in case iOS fired the timer late
// play a sound asynchronously if you like
}
// update the label with the remainding seconds
countdownLabel.text = [NSString stringWithFormat:#"Seconds: %.1f", difference];
}
- (IBAction)startCountdown {
countdownSeconds = 10; // Set this to whatever you want
startTime = [[NSDate date] retain];
// update the label
countdownLabel.text = [NSString stringWithFormat:#"Seconds: %.1f", countdownSeconds];
// create the timer, hold a reference to the timer if we want to cancel ahead of countdown
// in this example, I don't need it
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector (countdownUpdateMethod:) userInfo:nil repeats:YES];
// couple of points:
// 1. we have to invalidate the timer if we the view unloads before the end
// 2. also release the NSDate if don't reach the end
}
counterSecond = 10
NSTimer timer1 = [NSTimer scheduledTimerWithTimeInterval : 0.1
Target:self selector:#selector (countLabel) userInfo:nil repeats:YES];
-(void)countLabel:
counterSecond - 0.1;
self.timerLabel.text = [NSString stringWithFormat #"%f", counterSecond];
That should work.