Pass integer in userInfo of NSTimer - objective-c

I'm trying to pass an integer (testInt) through the userInfo field of NSTimer
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(count:) userInfo:testInt repeats:YES];
However I'm getting an incompatible types error message.
Does anyone know how to pass a number through to the count method?

You need to box it to an NSNumber:
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(count:)
userInfo:#(testInt) // <-- #() around your int.
repeats:YES];
Then in -count:
int testInt = [timer.userInfo intValue];

Related

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.

Can't invalidate, stop countdown NSTimer - Objective C

Problem is to stop NSTimer, for some reason [Timer invalidate] just not working...
Maybe my eyes are full of soap, but can't understand the reason why the timer didn't stop at 0, but go reverse counting -1, -2, -3 and so on...(((
I'm using epoch numbers as destination date. One more thing - my button "IBAction stop" with [Timer invalidate] works just fine - when i push it in simulator timer stops...
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
Timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateLabel) userInfo:nil repeats:YES];
}
- (IBAction) start {
Timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateLabel) userInfo:nil repeats:YES];
}
- (IBAction) stop {
[Timer invalidate];
Timer = nil;
}
-(void)updateLabel {
NSCalendar *calender = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
int units = NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *components = [calender components:units fromDate:[NSDate date] toDate:destinationDate options:0];
[dateLabel setText:[NSString stringWithFormat:#"%d%c %d%c %d%c %d%c", [components day], 'd', [components hour], 'h', [components minute], 'm', [components second], 's']];
destinationDate = [NSDate dateWithTimeIntervalSince1970:1355299710];
if (!destinationDate) {
[Timer invalidate];
Timer = nil;
}
}
As Totumus pointed out, your if statement condition !destinationDate always evaluates to false, so your updateLabel method never invalidates your timer.
You have another bug too:
You're creating a timer in viewDidLoad and storing a reference to it in your Timer instance variable.
Then you're creating another timer in start and storing a reference to it in your Timer instance variable, overwriting the reference to the timer you created in viewDidLoad without invalidating that older timer.
So now you have two timers running, but you don't have a reference to the older timer, so you can never invalidate it.
Note that the run loop has a strong reference to a scheduled (running) timer, so even if you remove all of your strong references to it, the timer keeps running. That's why the invalidate message exists: to tell the run loop to remove its strong reference to the timer.

Making alarm clock with NSTimer

I just went through trying to make an alarm clock app with local notifications but that didn't do the trick because I needed an alert to appear instead of it going into notification centre in iOS 5+
So far I've been struggling greatly with nstimer and its functions so I was wondering if anyone could help out.
I want when the user selects a time (through UIDatePicker, only time) for an alert to be displayed at exactly this time. I have figured out how to get the time from UIDatePicker but I do not know how to properly set the firing function of nstimer. This is what I have attempted so far, if anyone could help... be much appreciated. Thank you
Example (it keeps going into the function every second opposed to a certain time I told it too... not what I want):
NSDate *timestamp;
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setHour:2];
[comps setMinute:8];
timestamp = [[NSCalendar currentCalendar] dateFromComponents:comps];
NSTimer *f = [[NSTimer alloc] initWithFireDate:timestamp
interval:0
target:self
selector:#selector(test)
userInfo:nil repeats:YES];
NSRunLoop *runner = [NSRunLoop currentRunLoop];
[runner addTimer:f forMode: NSDefaultRunLoopMode];
As #Nate said change
NSTimer *f = [[NSTimer alloc] initWithFireDate:timestamp
interval:0
target:self
selector:#selector(test)
userInfo:nil repeats:YES];
To
NSTimer *f = [[NSTimer alloc] initWithFireDate:timestamp
interval:0
target:self
selector:#selector(test)
userInfo:nil repeats:NO]; //<-- Change here

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.