i have an application like timer in IOS. In the first screen i have button when i am click on it the timer is start and after some amount of time say 30 seconds i have run my application then i am stop my application (not in background) , i am terminating the application. I reopen my application after the timer shows 1 min, that is working fine but when i am changing the device system time then the timer shows different time.
NSTimeInterval oldTimeInterval = [[NSUserDefaults standardUserDefaults] doubleForKey:PunchInTimeWhenGoneBackgroundKEY];
NSTimeInterval currentTimeInterval = [[NSDate date] timeIntervalSince1970];
//if(currentTimeInterval>oldTimeInterval)
//{}
WORKING_TIME = WORKING_TIME + (currentTimeInterval - oldTimeInterval) + 1;
if(timerWorkTime!=nil)
{
[timerWorkTime invalidate];
timerWorkTime = nil;
}
timerWorkTime = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(funCalculateWorkingTime) userInfo:nil repeats:YES];
-(void)funCalculateWorkingTime
{
WORKING_TIME++;
workingHour = (int) WORKING_TIME / 3600;
WorkingMin = (int) WORKING_TIME / 60;
WorkingSecond = (int) WORKING_TIME % 60;
lblWorkingTime.text = [NSString stringWithFormat:#"%.2d : %.2d : %.2d",workingHour,WorkingMin,WorkingSecond];
earnedMoney = ((float)WORKING_TIME * HOURLY_RATE)/3600.0;
lblTotalEarned.text = [NSString stringWithFormat:#"$%.2f",earnedMoney];
// NSLog(#"time : %#",lblWorkingTime.text);
}
Please do help me.
Thanks in advance.
Don't use the system time for measurement. Store the time differences using UserDefaults or your own property list.
I don't know of a way to correct this without using a server to provide the timer functionality.
Without the app being able to run in the background, any timer that you set to save and check even when the app is not running will rely on the local device's system time. Any changes to the system time will mess that up.
My only thought would be to create a very simple server component where you could register a timer on the site, and then check with the server on startup to see what timers are available and how much time is left on them.
Edit: There is one option you could use, but it wouldn't be fool-proof. You can use CACurrentMediaTime() to get a time that is relative to the boot time. If the user changes time zones, or manually changes the system date, it would not be affected. It would still be relative to the last system boot. The down side is that it will throw off all your timers if the user restarts their phone.
Related
I was wondering if there was a clean way to do a countdown timer with Grand Central Dispatch (then display for example in a UILabel) that's synchronized to the system clock... based on a reference date? — So if my reference date is an NSDate that's 20 minutes from now, I'd have a countdown displayed in seconds (don't worry about formatting) that's synced to the system clock.
Just doing a quick version of this skips seconds every once in a while in case the update method call doesn't arrive on schedule.
To me this seems like a pretty basic question, so I'm looking ideally for a basic/clean solution besides increasing the update interval to be 10x or something.
Also, the solution shouldn't use NSTimer.
I would use a recursive method like this:
- (void) updateTimer{
NSDate *now = [NSDate date];
NSTimeInterval secondsToDate = [now timeIntervalSinceDate:self.referenceDate];
_timerLabel.text = [NSString stringWithFormat:#"%.0f", secondsToDate];
if( secondsToDate < 0 ){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self updateTimer];
});
}else{
NSLog(#"Timer triggered!");
}
}
You just have to call it the first time, then it will update the countdown every 0.1 seconds.
I want to use NSTimer to increase the number which show on a label.
Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.numberLabel = [[UILabel alloc]initWithFrame:CGRectMake(90, 90, 90, 30)];
[self.view addSubview:self.numberLabel];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(refreshText) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)refreshText{
NSDate *beginDate = [NSDate date];
static NSInteger a = 0;
a ++;
self.numberLabel.text = [NSString stringWithFormat:#"%ld",a];
if (a == 1000) {
NSDate *endDate = [NSDate date];
NSTimeInterval durationTime = [endDate timeIntervalSinceDate:beginDate];
NSTimeInterval intervalTime = self.timer.timeInterval;
NSLog(#"durationTime = %f",durationTime);
NSLog(#"intervalTime = %f",intervalTime);
[self.timer invalidate];
self.timer = nil;
}
}
and the console showed:
then I changed the timer's timeInterval from 0.01 to 0.001,the console showed:
What confused me is that why the durationTime is not 0.0000056 when the timeInterval is 0.001.What's more,is there a min value for NSTimer's timeInterval we can set?
The time period of an NSTimer is a value of type NSTimeInterval, while this provides sub-millisecond precision that does not help you. From the start of the NSTimer documentation:
Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
To use a timer effectively, you should be aware of how run loops operate. See Threading Programming Guide for more information.
A timer is not a real-time mechanism. If a timer’s firing time occurs during a long run loop callout or while the run loop is in a mode that isn't monitoring the timer, the timer doesn't fire until the next time the run loop checks the timer. Therefore, the actual time at which a timer fires can be significantly later. See also Timer Tolerance.
So the minimum time interval for an NSTimer is tied to the the length of a run loop iteration. While internal optimisations, if they exist, could fire a timer as soon as it is set if the interval is really small in general the shortest period you'll get is dependent on the remaining execution time of the run loop iteration in which the timer is set, which is pretty much indeterminate for general purpose programming.
If you really need a high-resolution timer (see #bbum's comment on your question) then you'll need to research that topic - just search something like "high resolution timing macOS" as a starting point.
HTH
There is a better approach to your problem. Use CADisplayLink instead of NSTimer. CADisplayLink allows you to update a UI every time the screen refreshes - as quickly as possible. There is no point to updating the UI more often than the screen can refresh it, so NSTimer is not the best tool fast UI updates.
func beginUpdates() {
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
displaylink.add(to: .current, forMode: .defaultRunLoopMode)
}
func tick() {
// update label
}
func endUpdates(){
self.displayLink.invalidate()
self.displayLink = nil
}
I'm starting out my iOS experience with a project that incorporates a large clock as a portion of the user interface. When I try to incorporate a user-adjustable toggle to switch from 12-hr to 24-hr time format, I get issues. After the initial user change of the on-screen toggle, the displayed time "flickers" between the 2 formats.
My approach:
viewDidLoad sets the attribute used for 12-hr time format and calls the startTimer method
startTimer method: NSDateFormatter is set. NSTimer is initiated with scheduled interval, passing the DateFormatter to the upDateTime method
updateTimer method checks current time and sets the on-screen
If the user, clicks the on-screen 12-hr/24-hr segmented control, the IBAction invalidates the timer and passes the desired time format attribute to the startTimer method
Testing/Observations:
Code below shows an approach where I modified the time format by setting the Locale and passing it to the DateFormatter. I thought this might be an issue conflicting with the device Locale. I tried using a custom Date Formatter (example #"hh:mm a") instead and had the same flickering issue.
NSTimer scheduled interval shows as 1 sec. I have this for testing, but saw same behavior at 0.1 sec.
NSLog calls within the updateTime method show that the DateFormatter object id and the displayed time are changing between successive loops, even though the user did not adjust the toggle switch.
I suspect this may have something to do with how the Timer is initiated and stopped. Perhaps the old "calls" with the previous format is still running in the background? I experimented with moving the [updateTimer invalidate] to a couple of places in the methods without success
Root Question:
Any suggestions or better approach to having an on-screen clock with a user switch for 12-hr vs. 24-hr time format? Any reason why the displayed time format keeps cycling?
Code:
- (void)startTimer:(NSString *)displayedClockMode {
// using locale within formatter overrides device system behavior chosen by user
NSString *localeValue = nil;
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
if([displayedClockMode isEqual:#"12-hr"]){
//then 12 hr format - based on US locale
localeValue = #"en_US_POSIX";
//[timeFormatter setDateFormat:#"hh:mm a"];
}
else { //assume no other value exists
// 24 hr format - based on GB locale
localeValue = #"en_GB";
//[timeFormatter setDateFormat:#"HH:mm"];
}
NSLocale *clockLocale = [NSLocale localeWithLocaleIdentifier:localeValue];
[timeFormatter setLocale:clockLocale];
[timeFormatter setTimeStyle:NSDateFormatterMediumStyle];
//stop timer before re-starting with new format
[self.updateTimer invalidate];
self.updateTimer = nil;
NSTimer *updateTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTime:) userInfo:timeFormatter repeats:YES];
}
- (void)updateTime:(NSTimer *)updateTimer {
NSDate *currentTime = [NSDate date];
NSLog(#"old display time: %#",self.displayedTime.text);
self.displayedTime.text = [updateTimer.userInfo stringFromDate:currentTime];
NSLog(#"new display time: %#",self.displayedTime.text);
NSLog(#"new timeformatter: %#",updateTimer.userInfo);
}
- (IBAction)displayedTimeMode:(id)sender {
[self.updateTimer invalidate];
self.updateTimer = nil;
NSString *timeFormat = nil;
if(self.displayedTimeToggle.selectedSegmentIndex == 0){
//if 0, then 12 hr format
timeFormat = #"12-hr";
}
else {
// is 1, 24 hr format
timeFormat = #"24-hr";
}
[self startTimer:timeFormat];
}
The problem is that you have two different updateTimers -- self.updateTimer the class variable and updateTimer the local variable. You're invalidating the class variable, but initializing and running multiple local NSTimers with different locales during each call to startTimer. That's why you see this "flickering" -- it's because multiple NSTimers are setting the label using different localeValues.
To fix this, change:
NSTimer *updateTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTime:) userInfo:timeFormatter repeats:YES];
to
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTime:) userInfo:timeFormatter repeats:YES];
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];
}
I'm working on app that sets an UILocalNotification. This is the first time I use it and I'd like to have some explanations to use it efficiently and in the best way.
Which is the bast way to countdown to the time of notification expiration? I'd like to show to the user an UILabel with the missing time, this UILabel updates every second and when I reopen my app it must show the timer.
I've done this in this way:
When I reopen my app or the recall the view that has to show the timer I check if there is a UILocalNotification with the userInfo I've setted previously then I subtract to its NSDate
the current time so I get the difference, and update the UILabel. I know it shows time in seconds but if this procedure it's good I will convert it into time.
Is there any best way to do this?
Here is my code:
UILocalNotification *countDownNotificationSetted;
NSArray *scheduledNotificationArray = [[UIApplication sharedApplication] scheduledLocalNotifications];
for (int i = 0; i<[scheduledNotificationArray count]; i++)
if ([[[[scheduledNotificationArray objectAtIndex:i] userInfo] objectForKey:#"ID"] isEqualToString:#"ParkNotification"]) {
countDownNotificationSetted = [scheduledNotificationArray objectAtIndex:i];
break;
}
NSDate *expringDate = [countDownNotificationSetted fireDate];
NSTimeInterval timeLeftToNotification = [expringDate timeIntervalSinceNow];
if(timeLeftToNotification == 0){
[countDownTimer invalidate];
}
datePicker.countDownDuration = timeLeftToNotification;
_countDownLabel.text = [NSString stringWithFormat:#"%.0f", timeLeftToNotification];
NSLog(#"%.0f", timeLeftToNotification);
I think it would be better if you would update your label with a timer as you already do , when you close the app, store that time locally in a file or user defaults and when the app launches again read from that file the time. Or if you want the time passed since the first openning of the app, just write the time in appDidFinishLaunching and get it from there every time . I don't see the benefit of the local notification here.
Hope this helps.
Cheers!