I suggest going about this in a different way:
@interface SNDViewController ()
@property (weak, nonatomic) IBOutlet UIButton *startButton;
@property (weak, nonatomic) IBOutlet UILabel *timerLabel;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSTimeInterval accumulativeTime;
@property (nonatomic, assign) NSTimeInterval currentReferenceTime;
@end
@implementation SNDViewController
EDIT: when the view loads, initialise self.accumulativeTime
and update the timer label. The initialisation of accumulativeTime variable should really be done in the appropriate init* method, e.g. initWithNibName:bundle:. It is at this point that you would read the timer value from core data.
- (void)viewDidLoad
{
[super viewDidLoad];
self.accumulativeTime = 300;
[self updateTimerLabelWithTotalTime:self.accumulativeTime];
}
- (IBAction)changeTimerState:(UIButton *)sender
{
if (self.timer == nil) {
self.currentReferenceTime = [NSDate timeIntervalSinceReferenceDate];
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES];
} else {
//Pause the timer and track accumulative time.
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
[self.timer invalidate];
self.timer = nil;
NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;
EDIT: subtract timeSinceCurrentReference from accumalitiveTime, because this is a countdown timer. Also added a comment for saving to core data if necessary.
self.accumulativeTime -= timeSinceCurrentReference;
[self updateTimerLabelWithTotalTime:self.accumulativeTime];
//Optionally save self.accumulativeTime to core data for future use.
}
NSString *buttonTitle = (self.timer != nil) ? @"Pause" : @"Resume";
[self.startButton setTitle:buttonTitle forState:UIControlStateNormal];
}
You don't need to store any unnecessary state, such as the showButtonValue
variable. Instead you can base your decision of whether to pause or resume on whether or not self.timer == nil. If there is no timer running, then grab the current reference time and start a new timer. The timer is scheduled to fire every 0.01 seconds, which will hopefully make it accurate to 0.1 seconds. You never need to change the button's title to "Start". It is either "Pause" or "Resume".
When the timer is paused by the user, we dispose of self.timer
and update the timer label with the most accurate time.
- (void)updateTimer:(id)sender
{
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;
EDIT: subtract timeSinceCurrentReference from self.accumulativeTime to get the totalTime (i.e. totalTime decreases as time goes by).
NSTimeInterval totalTime = self.accumulativeTime - timeSinceCurrentReference;
if (totalTime <= 0) {
totalTime = 0;
[self.timer invalidate];
self.timer = nil;
//What to do when we reach zero? For example, we could reset timer to 5 minutes:
self.accumulativeTime = 300;
totalTime = self.accumulativeTime;
[self.startButton setTitle:@"Start" forState:UIControlStateNormal];
}
[self updateTimerLabelWithTotalTime:totalTime];
}
Each time the timer is fired, we get the total time by finding the difference between now
and self.currentReferenceTime
and add it to self.accumulativeTime
.
- (void)updateTimerLabelWithTotalTime:(NSTimeInterval)totalTime
{
NSInteger hours = totalTime / 3600;
NSInteger minutes = totalTime / 60;
NSInteger seconds = totalTime;
NSInteger fractions = totalTime * 10;
self.timerLabel.text = [NSString stringWithFormat:@"%02u:%02u:%02u.%01u", hours, minutes % 60, seconds % 60, fractions % 10];
}
@end
The method - (IBAction)changeTimerState:(UIButton *)sender
is called by the UIButton on the "Touch Up Inside" event (UIControlEventTouchUpInside).
You don't need to do anything in viewDidLoad.
Also, and importantly, this is all done on the main thread. If anything gets in the way of the main thread updating the timer label, then the text visible to the user may be inaccurate, but when it is updated, you can be sure that it will be accurate again. It depends on what else your app is doing. But since all UI updates must be done on the main thread there is really no avoiding this.
Hope this helps. Let me know if anything is unclear.
(Xcode project available here: https://github.com/sdods3782/TVTTableViewTest)