I want my NSTimer to speed up each time it's run:
-(void)runtimer {
int delay = delay - 1;
[NSTimer scheduledTimerWithTimeInterval:(delay)
target:self
selector:#selector(timerTriggered:)
userInfo:nil
repeats:YES];
}
But this doesn't work. How can I make the delay keep getting smaller and smaller?
I needed this myself and wrote a component CPAccelerationTimer (Github):
[[CPAccelerationTimer accelerationTimerWithTicks:20
totalDuration:10.0
controlPoint1:CGPointMake(0.5, 0.0) // ease in
controlPoint2:CGPointMake(1.0, 1.0)
atEachTickDo:^(NSUInteger tickIndex) {
[self timerTriggered:nil];
} completion:^{
[self timerTriggered:nil];
}]
run];
This calls -timerTriggered: 20 times, spread out over 10 seconds, with ever-decreasing delays (as specified by the given Bézier curve).
Every time this method is run, you make a new variable called delay, then try to set it to itself minus 1. This is Undefined Behavior (the variable was not initialized to anything), and is likely to result in a garbage value for delay.*
You need to store the delay in an instance variable.
- (void) runTimer {
// You are declaring a new int called |delay| here.
int delay = delay - 1;
// This is not the same |delay| that you have declared in your header.
// To access that variable, use:
delay = delay - 1;
*A sinus infestation by evil-aligned supernatural beings is also a possibility.
You have to declare the delay somewhere, like in the class interface or as a static variable.
Also, create a new timer every time, instead of having it repeat.
int delay = INITIAL_DELAY;
-(void)runtimer{
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)(delay--) target:self selector:#selector(runTimer:) userInfo:nil repeats:NO];
}
You cannot change the fire interval of the timer once you have created it. If you want a different interval you must invalidate the previous timer (hence you should keep a reference to it), and create a new timer with a different interval.
Related
I want that my ball gets faster in my game. in pos you can choose the speed of the ball. But I want the ball getting faster every 5th second. pos = CGPointMake(5.0,4.0); after 5 seconds 5.0 should turn into 6.0 and 4.0 into 5.0.
I have a Timer which is named MainInt. MainInt is a counter and it counts the time how long you're playing without loosing.
There's also a label which shows the timer.
(IBOutlet UILabel *seconds; .h)
.m
-(void)viewDidLoad {
[lastTime setHidden:YES];
[super viewDidLoad];
// X Speed Y Speed
pos = CGPointMake(5.0,4.0); // <- these numbers (add 1 each every 5 sec.)
Speedy = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(doThis) userInfo:nil repeats:YES];
}
/*
-(void)doThis {
if(MainInt % 5 == MainInt) //True every 5th second
{
pos = CGPointMake();
}
}
*/
Well, for one thing, MainInt % 5 == MainInt is false after 5 seconds for the rest of eternity. 1-4 are the only values of MainInt that would ever make that true. Consider reviewing how the modulo operator works. Even if you change this to MainInt % 5 == 0, which is correct, you still have to ask yourself why you're checking for anything there regarding an external asynchronous time value. All this will do is, if the timer is not in synch, force it to not do anything every single time it is called because you already set it's delay between calls to 5 seconds. If you want everything to synch up nicely, you should probably have one main NSTimer or CADisplayLink in charge of a game loop where you can call methods that need to update your game's state every frame.
If you really want to do it this way with separate timers for everything and try to keep them in synch with your main time value, then here you go.
//replace timer line with this one
Speedy = [NSTimer scheduledTimerWithTimeInterval: 0.1 target:self selector:#selector(doThis) userInfo:nil repeats:YES];
//replace body of "doThis" with the following
if(MainInt % 5 == 0) //True every 5th second
{
pos = CGPointMake(pos.x + 1, pos.y + 1);
}
Also, you should follow some sort of naming convention and try to name things more clearly. If pos is the speed of the ball, shouldn't it be named velocity instead of position? What is Speedy, an instance variable? If so, why is it uppercase? Plus, Speedy is one of the most unclear name's I've ever heard; it's not a pet, it's a variable. Same thing for MainInt. In general, reserve uppercase names for class names and use camel case for instance variables, methods and functions. It'll make your code clearer. :)
I am pretty new to Cocoa, but I am trying to put a simple reaction game together. Therefore I need to generate the time intervall of the NSTimer randomly. Currently I have tried the code below.
int randomNumber = rand() %5;
changeColor = [NSTimer scheduledTimerWithTimeInterval:(randomNumber) target:self selector:#selector(changeBackground) userInfo:nil repeats:YES];
If you want resolution finer than 1 second, you should create a double from the random number. Perhaps like this:
int sourceRandom100x = rand() % 500; // i.e. 435
double randomInterval = sourceRandom100x/100.0 // 4.35
[NSTimer scheduledTimerWithTimeInterval:(randomInterval) ...
But the technique will use the same interval for every iteration. If you want a freshly-random interval every time, make the timer not repeat, and inside changeBackground, setup another one (via delegation to a more appropriately named new method, such as -(void) setupRandomBackgroundChangeTimerIfNecessary
Here's a code snippet I'm trying to get to work, but its loop won't stop the way that I want it to:
- (IBAction)methodName:(UIButton*)sender
{
[self loopMethod];
}
-(void) loopMethod
{
for( int index = 0; index < loopLimit; index++ )
{
//code I want to execute
[self performSelector:#selector(loopMethod)
withObject:nil
afterDelay:2.0];
}
}
The code just keeps looping even though I've made the for loop finite. What I want is for the code to execute, pause for two seconds, and then run the loop while the int value is less than the loopLimit I've set.
It's been hinted that this performSelector:withObject:afterDelay: method may not be the right thing to use here but I'm not sure why or what is better to use here.
Any illuminating suggestions?
What's happening here is that the loop is running as quickly as possible, and the performSelector:... calls are happening at that speed. Then, at 2.0001, 2.0004, 2.0010, ... seconds later, method gets called.
The other thing (now that you've edited to make clear that the performSelector:... is ending up calling the same method that it's in, is that the value of your loop's index variable isn't saved between calls. Every time loopMethod is run, the code in the loop starts from the beginning: index is set to zero and counts up. That means that every time the method is run, you end up with loopLimit new calls pending, 2 seconds from then. Each one of those calls in turn spawns a new set, and so on, ad infinitum.
Every run of the loop is in fact finite, but the loop keeps getting run. You need some way to signal that the loop needs to stop, and you can't do that entirely within the loop method. You could put the counter (your index variable) into an ivar; that would make its value persistent across calls to loopMethod, but I think you want to look into using an NSTimer that repeats:
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(method:) userInfo:nil repeats:YES];
If you stick this into an ivar, you can keep track of how many times it fires and stop it later. There's a number of posts already on SO about updating text fields in a loop, using a timer like this: https://stackoverflow.com/search?q=%5Bobjc%5D+update+text+field+timer
The reason it doesn't run every 2 seconds is because you are running through the loop, and shooting off that selector after a 2 second delay. In other words, there is no delay in between the loop. If i had to guess, it probably waits 2 seconds, then fires loopLimit times, correct?
For your function to work the way you want it to, it would need to be recursive.
-(void) methodName
{
if(index < loopLimit) {
//code you want to execute
[self performSelector:#selector(**methodName**) withObject:nil afterDelay:2.0];
}
index++;
}
This is a pretty awkward way to do this. An NSTimer is typically what you would use here instead, then you can stop the timer when you are done.
In a function you start the timer like this:
[self setIndex:0];
[self setTimer:[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(method:) userInfo:nil repeats:YES]];
then this method gets called every time:
-(void)method:(NSTimer *)timer {
if(index >= loopLimit) {
[[self timer] invalidate];
}
//Code you want to execute
}
I know that I should understand Apple's documentation for NSTimer, but I don't! I have also read a lot of questions about it but can not find one that suites my case. Well here it is:
The user enter hour and minutes through textfields. I convert those to integers and display them in a "countDownLabel".
How can i make the count down to zero?
It would be nice to show the seconds which is something that the user didn't imported but i guess it will not be hard to show.
How could somebody stop this procedure with a press of a UIButton?
I know that i am asking a lot...I would be really grateful if someone could help!
int intHourhs;
intHourhs=([textHours.text intValue]);
int intMinutes;
intMinutes=([textMinutes.text intValue]);
int *intSeconds;
intSeconds=0;
NSString *stringTotalTime=[[NSString alloc] initWithFormat:#"%.2i:%.2i:%.2i",intHourhs,intMinutes,intSeconds];
[countDownLabel setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:45]];
countDownLabel.text=stringTotalTime;
First you should calculate what your countdown time is, in seconds and put it in a instance variable (ivar)
NSTimeInterval totalCountdownInterval;
I think in order to keep good accuracy (NSTimer firing can be off by as much as 100ms and errors will add up) you should record the date at which the countdown started, and put it in another ivar:
NSDate* startDate = [NSDate date];
Then you can have a timer firing at regular (here 1 second) intervals calling a method on your class repeatedly
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(checkCountdown:) userInfo:nil repeats:YES];
And in that method you check the elapsed time against the total countdown time and update the interface
-(void) checkCountdown:(NSTimer*)_timer {
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSTimeInterval remainingTime = totalCountdownInterval - elapsedTime;
if (remainingTime <= 0.0) {
[_timer invalidate];
}
/* update the interface by converting remainingTime (which is in seconds)
to seconds, minutes, hours */
}
That's a big order. But start with baby steps. You need to create a method in your class which, when invoked (every N seconds), will update the labels you want updated. Then you arrange for a timer to invoke that method every N seconds.
There are several timer variants you might use, but for this case the most straight-forward is probably NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:.
Write your method to look like - (void)timerFireMethod:(NSTimer*)theTimer and the selector for the timer method above is #selector(timerFireMethod:).
my app has a function, it gets a value from a NSTextField and then declare the variable, like this:
- (IBAction)startTimer
//all the other code
int totalTime = secs + hoursInSeconds + minutesInSeconds
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerHandler) userInfo:nil repeats:YES];
then, i want to use the local variable totalTime in another function which processes the NSTimer.
- (void)timerHandler
//all other code
totalTime = totalTime - 1;
//invalidate timer when it reaches 0
if (totalTime == 0.0) {
[timer invalidate];
however, as the variable totalTime is a local variable, i cannot use the value, and i cannot move the code over as NSTimer calls it every 1 sec and as the user may change the variable (and thus redeclaring it).
so, is there any way i can get a local variable from a function and implement the variable in another function which can be changed dynamically? or can i implement a NSTimer countdown by just using one function
You could wrap the value in the timer's userInfo:
NSNumber *totalTimeNumber = [NSNumber numberWithInt:totalTime];
timer = [NSTimer scheduledTimerWithTimeInterval:... target:... selector:... userInfo:totalTimeNumber repeats:...];
Or just make it an instance variable.
Well, here's a fun one that works with local variables, instead of instance variables but only on Mac OS 10.6/iOS 4 and above:
-(IBAction)startTimer:(id)sender
{
// ensure, that the variables we'll capture in the block are mutable
__block int totalTime = ...
__block NSTimer *timer;
void (^timerBlock)() = ^{
if (--totalTime <= 0) { // this comparison is much less fragile...
[timer invalidate];
}
};
// If you'd call timerBlock() at this point you'll crash because timer contains junk!
// However, (since timer is declared as __block) we can give it a meaningful value now and have it updated inside of the block, as well:
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerHandler:) userInfo:timerBlock repeats:YES];
}
-(void)timerHandler:(NSTimer*)timer
{
((void (^)())[timer userInfo])(); // retrieve the block and run it
}
Caveat:
Since I'm sending this from my phone, I am not 100% sure about the cast in timerHandler:. But it's something along this line...
You should be able to omit the cast altogether, but will definitely see a warning then.