This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to make a periodic call to a method in objective c?
I am making an app where when the user touches the screen, the following method is called:
- (void)explode:(int)x
The user only has to touch the screen once, but I want the method to be called repeatedly every 0.1 seconds for 100 times, and then it should stop being called.
Is there a way of setting up a 'temporary' timer like this on a method where an integer is being passed?
You could pass a counter and the 'x' as into the timer's userInfo. Try this:
Create the timer in the method that's catching the touch event and pass a counter and the int 'x' into the userInfo:
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithCapacity:2];
[userInfo setValue:[NSNumber numberWithInt:x] forKey:#"x"];
[userInfo setValue:[NSNumber numberWithInt:0] forKey:#"counter"];
[NSTimer timerWithTimeInterval:0.1
target:self
selector:#selector(timerMethod:)
userInfo:userInfo
repeats:YES];
Create the timer method, check the userInfo's number count, and invalidate the timer after 100 times:
- (void)timerMethod:(NSTimer *)timer
{
NSMutableDictionary *userInfo = timer.UserInfo;
int x = [[userInfo valueForKey:#"x"] intValue];
// your code here
int counter = [[userInfo valueForKey:#"counter"] intValue];
counter++;
if (counter >= 100)
{
[timer invalidate];
}
else
{
[userInfo setValue:[NSNumber numberWithInt:x] forKey:#"x"];
[userInfo setValue:[NSNumber numberWithInt:counter] forKey:#"counter"];
}
}
Please also see the Apple docs on NSTimer:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nstimer_Class/Reference/NSTimer.html
Related
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.
I have this code to change the number in the NSString every five seconds.
How will I keep the numbers running in a loop? It now runs from 1 to 19 ,and stops at the last one (19) with a SIGABRT on the line: label.text = ...
How can I start with the first number displayed (0), before the first timer fires?
Here is the code:
-(IBAction) rotate3
{
NSString *number = [self.dayArray description];
NSArray *array = [[NSArray alloc] initWithObjects: #"0", #"1", #"2",..., #"19",nil];
number = #"0" ;
numberCount++ ;
self.dayArray = array;
[array release];
label.text = [NSString stringWithFormat:#"Day: %# ", [dayArray objectAtIndex :numberCount ]];
}
//and the timer
- (void)viewDidLoad
{
timer=[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(rotate3 )userInfo:nil repeats:YES];
}
Here is my answers:
1)I think, at the last one (19), the numberCount is 20 (numberCount++ ;).
2)Just set the value before scheduling the timer.
add this to your .h file, in your interface (that is, if it isn't already there)
{
NSInteger numberCount
}
Then in your viewDidLoad method, initialize numberCount and the label:
numberCount = 0;
label.text = #"0";
And in your time method, replace:
numberCount++
with
if(numberCount++ > 19)
numberCount = 0;
What is the "number" NSString used for, b.t.w.?
Why have dayArray?
why not something like
label.text = [NSString stringWithFormat:#"Day: %d", numberCount++];
if (numberCount>19) numberCount = 0;
I don't know what you have number count initialized to it should probably be -1 and also reinitialized to -1 . if you wish to iterate thru "Day: 0" ... "Day: 19"
It's not clear.
You could change the -(void)viewDidLoad timer to not repeat
timer=[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(rotate3 )userInfo:nil repeats:NO];
then, conditionally set it up again in the rotate3 method if you still want to change the text 5 seconds later.
try this:
#pragma mark - timer callback
-(IBAction)rotate3
{
[label1 setText:[dayArray objectAtIndex:numberCount]];
numberCount++;
if (numberCount >= [dayArray count])
numberCount = 0;
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
dayArray = [[NSArray alloc] initWithObjects:#"0",#"1",#"2",#"3", nil];
[label1 setText:[dayArray objectAtIndex:0]];
numberCount = 1;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(rotate3) userInfo:nil repeats:YES];
// Do any additional setup after loading the view, typically from a nib.
}
I have written the program, which chooses a random element from the array.
Once I press the button "Start", how do I loop that code?
I would like, that once a button "Start" is pressed, a new element from the array is chosen and written in the text field every 5 seconds.... Thanks for the answer.
#implementation MARandom
- (IBAction)Start:(id)sender {
NSArray *tones;
tones = [NSArray arrayWithObjects: #"F#0", #"Gb0", #"G0", #"G#0",#"Ab0",#"A0",#"A#0",#"Bb0",#"B0",
#"C1",#"C#1",#"Db1",#"D1",#"D#1",#"Eb1",#"E1",#"F1",#"F#1",#"Gb1",#"G1",#"G#1",#"Ab1",#"A1",#"A#1",#"Bb1",#"B1",
#"C2",#"C#2",#"Db2",#"D2",#"D#2",#"Eb2",#"E2",#"F2",#"F#2",#"Gb2",#"G2",#"G#2",#"Ab2",#"A2",#"A#2",#"Bb2",#"B2",
#"C3",#"C#3",#"Db3",#"D3",#"D#3",#"Eb3",nil];
i= (arc4random() % 48);
NSString *Tone;
Tone = [tones objectAtIndex: i];
[TextField setStringValue:(NSString *)Tone];
}
Please try this:
- (IBAction)Start:(id)sender {
[NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(updateTextFieldWithRandomNumber)
userInfo:nil
repeats:YES];
}
-(void)updateTextFieldWithRandomNumber{
NSArray *tones;
tones = [NSArray arrayWithObjects: #"F#0", #"Gb0", #"G0", #"G#0",#"Ab0",#"A0",#"A#0",#"Bb0",#"B0",
#"C1",#"C#1",#"Db1",#"D1",#"D#1",#"Eb1",#"E1",#"F1",#"F#1",#"Gb1",#"G1",#"G#1",#"Ab1",#"A1",#"A#1",#"Bb1",#"B1",
#"C2",#"C#2",#"Db2",#"D2",#"D#2",#"Eb2",#"E2",#"F2",#"F#2",#"Gb2",#"G2",#"G#2",#"Ab2",#"A2",#"A#2",#"Bb2",#"B2",
#"C3",#"C#3",#"Db3",#"D3",#"D#3",#"Eb3",nil];
i= (arc4random() % 48);
NSString *Tone;
Tone = [tones objectAtIndex: i];
[TextField setStringValue:(NSString *)Tone];
}
and also consider preparing the array at one place rather than the code that will be repeatedly called, perhaps at viewDidLoad.
Note: untested code, but should work.
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.
I constantly get malloc error : double freed....
The echoTime function is being called by a button. When I press the button again before the timer ends it gives me the malloc error. When I press the button after the timer finished to start it again, the simulator hangs.
Does anyone know what is wrong with the following piece of code:
-(IBAction)echoTime: (id) sender {
if (gameTimer != nil) {
[gameTimer invalidate];
[gameTimer release];
}
NSInteger secs = 1 * 60;
if (secs != 0) {
NSNumber *elapsedSeconds = [[NSNumber alloc] initWithInt:secs];
NSDictionary *myDict = [NSDictionary dictionaryWithObject:elapsedSeconds forKey:#"TotalSeconds"];
gameTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(echoIt:) userInfo:myDict repeats: YES];
}
}
-(void)echoIt: (NSTimer *) timer {
NSNumber *num = (NSNumber *) [[timer userInfo] valueForKey:#"TotalSeconds"];
seconds++;
NSInteger sec = [num integerValue] - seconds;
NSInteger minutes = sec / 60;
[gameTimeLabel setText:[NSString stringWithFormat:#"%02i:%02i", minutes, sec-(60*minutes)]];
if (sec == 0) {
[self playSound:#"Horn"];
[gameTimer invalidate];
}
}
NSNumber *elapsedSeconds = [[NSNumber alloc] initWithInt:secs];
In general dont alloc NSNumbers and a NSDictionary (and NSArray,NSSet) always retains the objects it is given (and releases them too when it should).
try...
NSNumber *elapsedSeconds = [NSNumber numberWithInt:secs];
It'll stop the retain cycle you've got and might stop the crash.
Also irrelevant but stylewise.
NSInteger secs = [[[timer userInfo] valueForKey:#"TotalSeconds"] intValue];
Is a little more efficient.