Changing a uilabel text at different time intervals - iphone

I am building a simple fitness/training app for iPhone. The user can pick a type of training session from a table which takes them to a view controller which contains a stopwatch. This view has labels populated from a mutable array.
I can get the stopwatch to work and the initial labels to populate from the array, but cannot work out how to get the labels to change at set time intervals. These intervals won't be regular so might be at 10mins, then 25, then 45 etc. I have been trying to do this via If statements where the timer == 25, for example. I'm sure this is a basic solution but I'm new to programming and can't work it out.
Timer code as follows:
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
timerLabel.text = timeString;
}
Start timer:
- (IBAction)startTimerButton:(id)sender {
if (timer == nil) {
startDate = [NSDate date];
// Create the stop watch timer that fires every 0.1s
timer = [NSTimer scheduledTimerWithTimeInterval:1.0/10
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
else {
return;
}
}

I'm not too sure what you're after. If you set the time interval to be 10 minutes/25 minutes etc. instead of 1.0/10 then in your time fired code you will know what the time interval for the timer is.
You can always query the time interval using the timeInterval instance method. Perhaps as follows.
- (void)updateTimer:(NSTimer*)timer
{
if ([timer timeInterval] == 10 * 60) // 10 minutes have elapsed
{
// Do something for this timer.
}
else if ([timer timeInterval] == 20 * 60) // 20 minutes have elapsed
{
}
}
Notice that I've added the timer as an argument to your updateTimer function. You would then have to use the #selector(update:) (with the colon at the end!) selector in your scheduledTimerWithTimeInterval method. When your callback selector gets called, it will then have passed to it the timer.
Or if you have a pointer to the timer you created in your 'startTimerButton' you could use that as follows:
- (void)updateTimer:(NSTimer*)timer
{
if (timer == myTenMinuteTimer) // 10 minutes have elapsed
{
// Do something for this timer.
}
else if (timer == myTwentyMinuteTimer) // 20 minutes have elapsed
{
}
}
Notice that in the second cause you're comparing the pointer to two objects and using that, where as in the first your comparing the value of a method for two objects, so the objects don't have to necessarily be a pointer to the same object in order to be evaluated to be true.
Hope this helps!

Related

UILabel with current time

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)?
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.
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.
//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.
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.
one second is the right answer.
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;
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)

creating a timer for a level in iphone game

Im trying to add a timer to my game so that the user knows how long they have spent playing a level. Ive figured out that I can initialize a timer the following way:
bool showTimer = YES;
NSDate startDate;
UILabel timerLabel; // initialized in viewDidLoad
-(void) showElapsedTime: (NSTimer *) timer {
if (showTimer) {
NSTimeInterval timeSinceStart;
if(!startDate) {
startDate = [NSDate date];
}
timeSinceStart = [[NSDate date] timeIntervalSinceDate:startDate];
NSString *intervalString = [NSString stringWithFormat:#"%.0f",timeSinceStart];
timerLabel.text = intervalString;
if(stopTimer) {//base case
[timer invalidate];
}
}
}
- (void) startPolling {
[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(showElapsedTime:) userInfo:nil repeats:YES];
}
I start the startPolling method in the viewDidLoad. When I run the app, I do see the timer and it tracks the time but when I exit the app and re-enter it, the timer doesnt pause. I'm also not sure how to handle going to another view (like the options menu) and then coming back to this view. I understand NSDefaults and NSCoding and I see how I could save the current value on the timer as a Coding object, keeping a seperate key-value pair in a plist for every level but this seems cumbersome.
Is there a better way to keep track of how long the user spends in a level?
Instead of doing the calculation (subtracting the start time from the current time) every time, since all you care about is an elapsed time, just have a variable like NSTimeInterval elapsedTime that you start at 0 and add time to every time that the timer fires. (If you want to track it to 0.1 seconds like in your example, just divide by 10 before displaying it.) This way, you can pause it whenever you want and it will just continue on from where it was before when it starts up again.

Adding pause functionality for NSTimer

First, here is some working code for a stopwatch in Xcode. I got two buttons, Startand Stop. Their titles change when the buttons are pressed. Now I want to add a pause functionality. I know that there are many threads about this, but (I don't know why) I was not able to get it working.
So what is the best approach to implement this function in my code?
I already tried to use a pause date and subtract it from my NSTimeIntervalbut got negative values ...
Thanks so far!
So I did this:
//use timer to update the ui only, store start date (NSDate) and time interval elapsed (NSTimeInterval)
//this is called when the timer is not running, nor paused - a sort of 'first run' function
-(void)onTimerStart
{
//zero the time elapsed
time_passed = 0;
//save the starting date
start_date = [NSDate date];
//start a timer that will update the ui in the onTimer function
timer = [NSTimer timerWithTimeInterval:1.0/10.0 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
}
//called when the timer is running to pause it
-(void)onPause
{
//calculate the time that passed ( += not = )
time_passed += [[NSDate date] timeIntervalSinceDate: start_date];
//stop the timer
[timer invalidate];
//you can get rid of the start date now (using ARC ........)
}
//restarting the timer that was paused
-(void)onUnpause
{
//get new start date
start_date = [NSDate date];
//start the timer to update ui again
timer = [NSTimer timerWithTimeInterval:1.0/10.0 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
}
//use this function if you are stopping - not pausing! - the timer.
-(void)onReset
{
//stop timer
[timer invalidate];
//calculate the final time that passed
//THE NEXT LINE IS PROBABLY WRONG AND HAS TO BE time_passed = 0; THEN IT WORKS
time_passed += [[NSDate date] timeIntervalSinceDate: start_date];
//get rid of the start date now
}
//use this function to update UI - this is what gets called by the timer
-(void)onTimer
{
//when timer ticks use ([[NSDate date] timeIntervalSinceDate: start_date] + time_passed)
//to get the amount of time passed for display
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self onTimerStart];
}
WORKING CODE:
#pragma mark - Timer
- (void)timer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
timerLabel.text = timeString;
}
#pragma mark - Stopwatch
- (IBAction)onStartPressed:(UIButton *)sender
{
if ([sender.titleLabel.text isEqualToString:#"Start"] && (![timer isValid]) && ([timerLabel.text isEqualToString:#"00:00.0"]))
{
startDate = [NSDate date];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(timer)
userInfo:nil
repeats:YES];
}
}
- (IBAction)onStopPressed:(UIButton *)sender
{
if ((![timer isValid]) && ([sender.titleLabel.text isEqualToString:#"Reset"]))
{
[timer invalidate];
timer = nil;
timerLabel.text = #"00:00.0";
[sender setTitle:#"Stop" forState:UIControlStateNormal];
}
if (([timer isValid]) && ([sender.titleLabel.text isEqualToString:#"Stop"]))
{
[timer invalidate];
timer = nil;
[sender setTitle:#"Reset" forState:UIControlStateNormal];
}
}
There is no pause/resume functionality built into NSTimer, so you have to implement something along the lines of:
//THIS IS PSEUDOCODE
//use timer to update the ui only, store start date (NSDate) and time interval elapsed (NSTimeInterval)
//this is called when the timer is not running, nor paused - a sort of 'first run' function
- onTimerStart:
{
//zero the time elapsed
time_passed = 0;
//save the starting date
start_date = [NSDate date];
//start a timer that will update the ui in the onTimer function
[timer start]
}
//called when the timer is running to pause it
- onPause
{
//calculate the time that passed ( += not = )
time_passed += [[NSDate date] timeIntervalSinceDate: start_date];
//stop the timer
[timer invalidate];
//you can get rid of the start date now
}
//restarting the timer that was paused
- onUnpause
{
//get new start date
start_date = [NSDate];
//start the timer to update ui again
[timer start];
}
//use this function if you are stopping - not pausing! - the timer.
- onReset
{
//stop timer
[timer invalidate];
//calculate the final time that passed
time_passed += [[NSDate date] timeIntervalSinceDate: start_date];
//get rid of the start date now
}
//use this function to update UI - this is what gets called by the timer
- onTimer
{
//when timer ticks use ([[NSDate date] timeIntervalSinceDate: start_date] + time_passed)
//to get the amount of time passed for display
}

running a time interval computation in the background

I have a NSTimeInterval which is basically
NSTimeInterval interval = [date1 timeIntervalSinceDate: [NSDate date]];
I want this computation to be always running in the background when I am in a view X, so I can display a timer counting down in a UILabel.. how do I do this? It's like in the groupon iphone app, but it doesn't show it in details of seconds
You could use an NSTimer to get a method called at a set time interval (however this is all performed on the main thread, not on a background thread):
- (void)setupTimer {
//Start a timer to call update updateInterval: every 1 second
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateInterval:)
userInfo:nil
repeats:YES];
}
- (void)updateInterval:(NSTimer *)timer {
NSTimeInterval interval = [date1 timeIntervaleSinceDate:[NSDate date]];
//Do other things like updating the label
}

Simple timer in iPhone

I'm trying to use timeIntervalSinceReferenceDate and I'm not quite sure if I understand it correctly. I basically have a button to calculate the difference in time between when the start and stop button is pressed.
- (IBAction)startButtonPressed {
startButtonFlag = !startButtonFlag; // first time through, startButtonFlag turns on
if (startButtonFlag) { // timer starts
[startButton setTitle:#"Stop" forState:UIControlStateNormal];
startTime = [NSDate timeIntervalSinceReferenceDate];
NSLog(#"start time: %d", startTime);
}
else { // timer stops
[startButton setTitle:#"Start" forState:UIControlStateNormal];
stopTime = [NSDate timeIntervalSinceReferenceDate];
NSLog(#"stop time: %d", stopTime);
elapsedTime = stopTime - startTime;
NSLog(#"elapsed time: %d", elapsedTime);
}
}
I don't quite understand the output. My sample output is:
start time: 558828278
stop time: 581239552
elapsed time: -1610612736
I pressed the stop button shortly after (5 seconds or so) after I pressed start. I was expecting that the stop time would be more like 558828283 so when I subtracted the two times, to see how much time has elapsed, I would get 5 seconds. Am I misunderstanding how the class method works? Thanks.
Are startTime, stopTime, and elapsedTime declared as type NSTimeInterval or double?
In that case, you should use %f instead of %d (which is for ints).
you could try something like this instead:
NSDate* start = [NSDate date];
...
NSDate* stop = [NSDate date];
NSLog(#"took %lf seconds", [stop timeIntervalSinceDate:start]);
Your using the wrong function:
[NSDate timeIntervalSinceReferenceDate];
Creates and returns an NSDate object set to a given number of seconds from the first instant of 1 January 2001, GMT.
You should be using something like
dateWithTimeIntervalSinceNow
Creates and returns an NSDate object set to a given number of seconds from the current date and time.