Reputation: 55544
I have a UILabel that I want to show the current time (HH:mm) (the same time as in the status bar).
How do I update the label to change to the new time? If I schedule an NSTimer with an interval of 60 seconds, then label could be out of time by up to a minute, if the timer fires just before the system time's minute changes?
Would it be ok to set the timer's interval to 1 second, or will that use more resources than necessary? Or is there another way to make sure the label will stay in sync with the status bar clock (preferably exactly, but 1 second lee way is ok)?
Upvotes: 6
Views: 3104
Reputation: 1460
Second-precision synchronised updates without gettimeofday or blocks.
Weak pointer and dispatch_async
prevent retain cycle.
- (void)updateTimeLabel
{
if (!timeFormatter) {
timeFormatter = [NSDateFormatter new];
timeFormatter.dateStyle = NSDateFormatterNoStyle;
timeFormatter.timeStyle = NSDateFormatterShortStyle;
}
NSDate *currentTime = [NSDate date];
NSTimeInterval delay = [[currentTime nextMinute] timeIntervalSinceDate:currentTime];
timeLabel.text = [timeFormatter stringFromDate:currentTime];
__weak id weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf performSelector:@selector(updateTimeLabel) withObject:nil afterDelay:delay];
});
}
Get next minute with 0 seconds.
@implementation NSDate (Utils)
- (NSDate *)nextMinute {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [calendar components:(NSCalendarUnit) NSUIntegerMax fromDate:self];
comps.minute += 1;
comps.second = 0;
return [calendar dateFromComponents:comps];
}
@end
I think there is a retain cycle with block in Richard's answer (it may be fixed with __weak+dispatch_async)
Upvotes: 0
Reputation: 502
//Create a timer...
timer = [NSTimer scheduledTimerWithTimeInterval:0.25
target:self
selector:@selector(tick:)
userInfo:NULL
repeats:YES];
//Function to update time
- (void)tick:(NSTimer*)t
{
NSDate *now = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"HH:mm:ss"];
NSString *timeString = [dateFormatter stringFromDate:now];
[uilabel setText:timeString];
}
your timer will always be "delayed" by some time, since there is no "delegate" functions to call to make such feature.
I would stick with timer, but dispatch as Richard J. Ross III mentioned is valid as well.
Upvotes: 1
Reputation: 55543
Dispatch is your friend:
void runBlockEveryMinute(dispatch_block_t block)
{
block(); // initial block call
// get the current time
struct timespec startPopTime;
gettimeofday((struct timeval *) &startPopTime, NULL);
// trim the time
startPopTime.tv_sec -= (startPopTime.tv_sec % 60);
startPopTime.tv_sec += 60;
dispatch_time_t time = dispatch_walltime(&startPopTime, 0);
__block dispatch_block_t afterBlock = ^(void) {
block();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60), dispatch_get_main_queue(), afterBlock);
};
dispatch_after(time, dispatch_get_main_queue(), afterBlock); // start the 'timer' going
}
This will synchronize down to the nanosecond and only call when the minute changes. I believe that this is the optimal solution for your situation.
Upvotes: 5
Reputation: 7227
Maybe you can set two timers. The first one (fired once) is used to synchronize the second timer with interval of 60s, which make it fired when the system time comes to HH:00;
Upvotes: 0
Reputation: 857
Would it be ok to set the timer's interval to 1 second, or will that use more resources than necessary?
Depends on what you're doing. If you're calculating the first million digits of pi, or rendering several hundred 3-D objects, you'll need every processor cycle you can spare. If the CPU is idling most of the time, you may as well use those cycles to make your interface look nice.
Upvotes: 1
Reputation: 3558
I think I would run a NSTimer with a 1 second delay in a background thread, have the method it runs check the current time (HH:mm) to the current time displayed in the label. If it matches throw it away, if it's new, update the label.
If you are concerned about performance, after you return the first time, find out how many seconds away from the next minute it is, and have it run the timer run for that long. Then after the fist update have the timer run on 60 second intervals.
Upvotes: 0