I'm working with an app that processes device motion events and updates interface in 5 second increments. I would like to add an indicator to the app that would display the total time the app has been running. It seems that a stopwatch-like counter, like the native iOS Clock app is a reasonable way to count time that the app has been running and display it to the user.
What I'm not sure of is the technical implementation of such a stopwatch. Here's what I'm thinking:
if I know how long between interface updates, I can add up seconds between events and keep a count of seconds as a local variable. Alternatively, a 0.5 second interval scheduled timer can provide the count.
If I know the start date of the app, I can convert the local variable to date for each interface update using [[NSDate dateWithTimeInterval:(NSTimeInterval) sinceDate:(NSDate *)]
I can use a NSDateFormatter with a short time style to convert the updated date to a string using stringFromDate method
The resulting string can be assigned to a label in the interface.
The result is that the stopwatch is updated for each "tick" of the app.
It appears to me that this implementation is a bit too heavy and is not quite as fluid as the stopwatch app. Is there a better, more interactive way to count up time that the app has been running? Maybe there's something already provided by iOS for this purpose?
If you look in the iAd sample code from Apple in the basic banner project they have a simple timer:
NSTimer *_timer;
_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
and the the method they have
- (void)timerTick:(NSTimer *)timer
{
// Timers are not guaranteed to tick at the nominal rate specified, so this isn't technically accurate.
// However, this is just an example to demonstrate how to stop some ongoing activity, so we can live with that inaccuracy.
_ticks += 0.1;
double seconds = fmod(_ticks, 60.0);
double minutes = fmod(trunc(_ticks / 60.0), 60.0);
double hours = trunc(_ticks / 3600.0);
self.timerLabel.text = [NSString stringWithFormat:#"%02.0f:%02.0f:%04.1f", hours, minutes, seconds];
}
It just runs from start up, pretty basic.
Almost what #terry lewis suggested but with an algorithm tweak:
1) schedule a timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
2) when the timer fires, get the current time (that's the tweak, don't count ticks because if there is wobble in the timer, tick counting will accumulate the error), then update the UI. Also, NSDateFormatter is a simpler and more versatile way to format time for display.
- (void)timerTick:(NSTimer *)timer {
NSDate *now = [NSDate date];
static NSDateFormatter *dateFormatter;
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"h:mm:ss a"; // very simple format "8:47:22 AM"
}
self.myTimerLabel.text = [dateFormatter stringFromDate:now];
}
Related
I have a determinate progress bar which looks like the following:
NSInteger progressValue;
NSTimer *timerObject;
- (void)incrementProgressBar {
// Increment the progress bar value by 1
[progressBar incrementBy:1.0];
progressValue++;
[progressBar setDoubleValue:progressValue];
}
And then I call it by:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(incrementProgressBar) userInfo:nil repeats:YES];
My question: Is it possible to increment the scheduledTimerWithTimeInterval for each iteration?
So for example if the progressValue was 1 then scheduledTimerWithTimeInterval would be 1, but if progressValue was 2 then scheduledTimerWithTimeInterval would be 2, etc.
Basically what I'm trying to do is progressively slow the progress bar down the closer it gets to 100. There's probably some better way or recommendation for doing this, but it's essentially what I'm looking for, thanks.
Use -fireDate:(NSDate*) to set the next date when the NSTimer will fire. For further explanation of the API, go here.
For example, you can achieve that as follows:
NSDate *currentDate = [NSDate date];
NSDate *newDate = [NSDate dateWithTimeInterval:(NSTimeInterval)progressValue sinceDate: currentDate];
timerObject.fireDate = newDate;
I am creating a game in Spritekit, and I am trying to set up my game in a way that when a player loses all of their lives they have to wait 30 minutes for one of their lives to be restored so that they can play again. I tried using NSTimer to do this but I figured UINotification will be more effective since I want this timer to run whether or not the app is terminated, in the background, being used or not being used. I'm having problems setting this up though.
I have the following code written thus far, when the user reaches the GameOverScene
-(instancetype)initWithSize:(CGSize)size {
if (GameLives < 5 ) {
alarm = [[UILocalNotification alloc] init];
alarm.fireDate = [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes];
alarm.timeZone = [NSTimeZone localTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:alarm];
alarm.repeatInterval = NSCalendarUnitHour;
NSLog(#"AlarmFireDate = %#", alarm.fireDate);
}
}
The alarm.firedate shows up correctly in the NSLog when I reach the GameOverScene but when I close down my app and restart it, it shows up as null in my view controllers and never fires. How do I get my app to automatically update the user's lives in the background once the notification is scheduled regardless of whether the user is using the app or not? Should it be run in my app delegate?
Should some type of NSDate comparison like the one below run somewhere?
if ([[NSDate date] compare:allarm.fireDate] == NSOrderedDescending) {
GameLives = GameLives + 1;
NSLog(#"SUCCESS");
NSLog(#"COUNT = %lu", (long)GameLives);
}
else if ([[NSDate date] compare:allarm.fireDate] == NSOrderedAscending){
GameLives = GameLives + 1;
NSLog(#"FAILURE");
NSLog(#"COUNT = %lu", (long)GameLives);
}
else if ([[NSDate date] compare:allarm.fireDate] == NSOrderedSame){
NSLog(#"SAME");
NSLog(#"COUNT = %lu", (long)GameLives);
}
I'd be most grateful to anybody that can offer help.
EDIT: RESPONSE TO THE ANSWERS BELOW
I wrote the following code for the NSTimer and the timer starts when the game reaches the GameOver Scene.
-(void)restoreLives{
thirtyNewMinutes = 60 * 30;
update = [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes];
if ([[NSDate date] compare:update] == NSOrderedDescending) {
NSLog(#"date1 is later than date2");
NSLog(#"SUCCESS");
NSLog(#"CurrentDate: %#", [NSDate date]);
// LifeText = #"Restored";
GameLives = GameLives + 1;
NSLog(#"LIVES = %ld", (long)GameLives);
// NSLog(#"Level 2 HighScore, %d", _Level1HighScoreNumber);
} else if ([[NSDate date] compare:update] == NSOrderedAscending) {
NSLog(#"date1 is earlier than date2");
NSLog(#"FAILURE");
NSLog(#"CurrentDate: %#", [NSDate date]);
NSLog(#"LIVES = %ld", (long)GameLives);
// Lives = 5;
// NSLog(#"dates are the same");
}
if (GameLives < 4){
[LifeTimer invalidate];
}
And then I created an NSTimer to run the method.
-(void)CheckTime{
LifeTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(restoreLives) userInfo:nil repeats:YES];
}
How would I get it to save the target time that you're speaking of?
And, hopefully I'm not overthinking this but from another perspective if I wanted to compare the current NSDate with the [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes]; wouldn't I need to save the original date of [NSDate dateWithTimeIntervalSinceNow:thirtyNewMinutes]; when it was originally called so that if the app terminates and the timer runs the code again it compares it to the original time the code was called and doesn't reset the NSDate and compare it to 30 minutes from the time the user restarts the app and the timer begins again.
i.e. 7:15
NSDate comparison to update = [NSDate dateWithTimeIntervalSinceNow:thirtyMinutes];
is called. And timer is set to update lives at 7:45.
7:30 User terminates their app and restarts it at 7:35
When the NSTimer runs again won't it reset the time to be 30 minutes from 7:35 since it's 30 minutes from now? If this is the case how would I go about saving the original date? Please let me know, keep in mind I'm still a beginner with Objective C
A local notification works well if you want to inform the user of something, and can have a payload which you could use to keep track of the information you need, but it's probably not the best solution for you to do your timing work. If the user disables notifications for your app, it would break your functionality.
Instead, when it comes to keeping track of events based on a time, it's best to rely on date comparisons along with timers.
While your app is open, you should use an NSTimer to trigger what you need to do, which I think you have covered.
When you app goes to the background or terminates you should save the target time in some kind of persistent storage (NSUserDefaults, for example). When you app is relaunched or returns from the background, you should compare against that date and either start up the timer or trigger your code that the timer would fire yourself.
Try this to save/restore the date:
// Save the date with the key "NextFireDate"
[[NSUserDefaults standardUserDefaults] setObject:nextFireDate forKey:#"NextFireDate"];
// This forces the values to be saved by the system, which normally only happens periodically
[NSUserDefaults standardUserDefaults] synchronize];
...
// Retrieve the date with the key "NextFireDate"
NSDate *nextFireDate = [[NSUserDefaults standardUserDefaults] objectForKey:#"NextFireDate"];
You'd call the first whenever you go to the background/terminate (also invalidate your current timer) and the second when you finish launching or return from the background (and start a new timer with the retrieved date). NSUserDefaults is basically just a dictionary (that can accept scalars without having to box them yourself) that persists as long as your app is installed.
while(...condition...)
{
//do something
NSDate *date = [NSDate date];
NSTimeInterval milliseconds = [date timeIntervalSince1970];
[NSThread sleepForTimeInterval:0.2];
date = [NSDate date];
NSTimeInterval milliseconds1 = [date timeIntervalSince1970];
NSLog(#"**** time taken : %f",milliseconds1-milliseconds);
//calling some method
}
After 2 minutes of execution of this loop, the "time taken" increases from 200ms to 10s. Why? What is the problem ?
Assuming you are running in a multithreaded environment, answering your question would require a lot more information than you are giving us. Suffice to say, there is no guarantee that your sleeping thread will run "calling some method" exactly 200ms later, because that depends on what your other threads are doing.
Sleeping a thread like your example is generally considered a bad idea. Here's another way to accomplish what I think you are trying to do, but with ... better citizenship.
- (void)loopIfNeeded
{
if (...condition...) {
// do something
// ... and then call -someMethod ~200ms later
[self performSelector:#selector(someMethod) withObject:nil afterDelay:0.2];
}
}
- (void)someMethod
{
// whatever some method does
[self loopIfNeeded]; // continue loop
}
Finally, some observations:
-[NSDate timeIntervalSince1970] returns seconds (and fractions thereof, docs here), not milliseconds (although it has ms precision).
For timing, I find CFAbsoluteTime to be lighter weight:
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
// do your thing
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
CFTimeInterval elapsed = start - end; // or just NSLog(#"elapsed %f", start - end);
The general comments being made suggesting this is not the way to pause your loop are correct, you should look at other ways to do this - using blocks, GCD and routines such as dispatch_after is one approach.
However, to answer your actual question: sleeping is imprecise, you sleep for at least the time specified but maybe longer. E.g. from the sleep(3) manual page:
System activity may lengthen the sleep by an indeterminate amount.
Having said that, extend 0.2s to 10s does seem quite a long stretch. Look for other activity that might be kicking in and slowing you down, if you are on Mavericks consider its aggressive actions (app nap, timer coalescing, etc.).
But really, unless this is just test code look at partitioning it into A: test condition do part 1 and B: do part 2 then call A, or some other way, and use a timer callback or GCD to handle the pause.
HTH
Your code is wrong!!!
while(...condition...)
{
//do something
NSDate *date = [NSDate date];
NSTimeInterval milliseconds = [date timeIntervalSince1970];
[NSThread sleepForTimeInterval:0.2];
date = [NSDate date];
NSTimeInterval milliseconds = [date timeIntervalSince1970]; // here should be milliseconds1
NSLog(#"**** time taken : %f",milliseconds1-milliseconds);
//calling some method
}
You're messing up your variables milliseconds and milliseconds1.
I write a simple application for mac osx that works with system time to calculate some data, i want to run an specific routine every time clock come to 12:00
Is there any system api for this?
How i do this?
actually i have a status bar item in my app that show current day in month in any selected calendar like gregorian and islamic, and i want to this value will be sync with system time
If your routine is the method - routine:(NSTimer *)timer of object:
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; // Current time
NSTimeInterval then = ((int)now + 86400) % 86400; // Next 12am
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:then];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:date interval:86400 target:object #selector(routine:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
I have a timer that I start and stop multiple times. How do I accumulate the values once the timer has stopped each time?
When the timer is started note the start time. Whenever the timer fires add the difference between the current time and the start time to your total. Then reset the start time to the current time. When the timer is stopped, you either add the difference between the current time and start time to the total or don't depending on your application, but you don't reset the start time. When the timer is restarted, don't add anything to the total, but reset the start time.
So something like this:
startTime = [[NSDate alloc] initWithTimeIntervalSinceNow]; // This is an NSDate* in your class
Then when your timer fires:
NSDate* currentTime = [NSDate dateWithTimeIntervalSinceNow];
accumulatedTime += [currentTime timeIntervalSinceDate:startTime]; // accumulatedTime is an NSTimeInterval in your class
[startTime release];
startTime = [currentTime retain];
etc.
I figured it out... here is how I did it:
NSTimeInterval *accumulatedTime;
accumulatedTime += 1.0; // add one second
This is assuming of course that you have the supporting code to start, pause and stop the timer.