Synchronized countdown timer with `NSTimeInterval` without skipping seconds? (presumably due to rounding) - ios

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.

Related

What's the minimum valid time interval of an NSTimer?

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
}

How to use block handler for getting return value when timer running in objective-c (IOS)

I have a timer that run on 10 minutes. I want to get time at the 5th minute for some actions before end time (the 10th minute). May be not use block handler if you have another better solution.
You can use "performSelector" to do whatever you want at the 5th minute.
NSTimeInterval duration = 10 * 600;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self func];
});

IOS timer application

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.

Triggering a method at times stored in Array using NSTimer

I have an Array that have stored times. I want NStimer to trigger at times stored in that array. For example, in array a[] i have 2, 5 and 10 seconds and want NSTimer to trigger a method at these times. I do have another NSTimer that keep updating the time instance variable every second.
A simple solution would be to loop through your array and schedule method calls with GCD
for (NSNumber *time in array) {
NSTimeInterval delay = [time doubleValue];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
<#code to be executed after a specified delay#>
});
}

Add a running countup display timer to an iOS app, like the Clock stopwatch?

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];
}

Resources