Getting Started - Part 4: Challenge Solutions and Using NSDate Class
/Getting Started Series by Nick Schneider
Welcome back for the 4th part of our Getting Started series! Today we are going to solve the challenges set forth in Part 3. If you want to start where we left off, grab the the project here.
We will go through each of the challenges we asked you to try out. There may be multiple ways to solve these, but this post will go through our thought process.
Challenge 1 - Autofill Time In Minutes
Add functionality so that when you hit the stop button, timeInMinutesTextField is automatically updated. Format the number to be in minutes and have two decimal places.
Solution
This is a pretty straight forward task. We really just want to add an additional functionality to the IBAction associated with stopButtonPressed . However, there may be other times where we want to update our timeInMinutesTextField, so we will write a separate function to grab the time and convert it to seconds. Open up ViewController.h and declare a void function to handle updating the text field:
- (void)updateTimeInMinutesTextFieldWithCurrentTime;
Head over to ViewController.m and implement the function as follows:
- (void)updateTimeInMinutesTextFieldWithCurrentTime {
float currentTime = _currentTimeInSeconds / 60.0 ;
self.timeInMinutesTextField.text = [NSString stringWithFormat:@"%.2f", currentTime];
}
Where we cast the currentTimeInSeconds as a float in minutes, and then turn it into a formatted string with only two decimal places for out timeInMinutesTextField . The last thing we need to do is add a call for this function to the stopButtonPressed function. Go ahead and modify the function to read:
- (IBAction)stopButtonPressed:(id)sender {
[_myTimer invalidate];
[self updateTimeInMinutesTextFieldWithCurrentTime];
}
All we do here is call the function we just wrote every time the user hits stop. Build and run the app and see how it works!
Challenge 2 - Time Label Formatting
Most of the time when reading and testing your reading rate, you read for a few minutes or some time less than a full hour. Make it so the stopwatchTimeLabel has the format of mm:ss until the timer has been running for an hour, where the format changes to the current format.
Solution
Another pretty easy problem. We will just need to check if we the timer has reached an hour or not. We can add the conditional check to our formattedTime function, and return separate formats for less than and greater than one hour. Go ahead and modify formattedTime to read:
- (NSString *)formattedTime:(int)totalSeconds
{
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
NSString *formattedString = @"";
if (hours < 1) {
formattedString = [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
} else {
formattedString = [NSString stringWithFormat:@"%02d:%02d:%02d",hours, minutes, seconds];
}
return formattedString;
}
Notice that we use a temporary value in the conditional. We need to do this so that we have only one return statement.
Build and run your project and see how it acts. Instead of waiting an hour for the transition, change the value you initialize _currentTimeInSeconds to something closer to an hour, say 3590 . You probably noticed that when you started the app, the stopwatch still reads 00:00:00. You can go back and change the default in Main.storyboard under the Attributes Inspector. When all is said and do, you should have a view that is a little bit nicer to view. (Remember to reset your initialization to 0!)
Challenge 3 - Track Time when app is Closed
If you were to hit the home button on the simulator while the timer is running, you will notice that when you come back to the app, the time continues where you left off. This means you are not timing the absolute time, just the time the app is running and active. Find a way to track the full time. Hint: You may want to consider using the NSDate class.
Solution
This problem takes a little bit more and introduces us to the NSDate class. We want to keep track of the total time, so we need to store the real time when we hit start, and then calculate the currentTimeInSeconds based on the difference and whatever time was on the clock when we hit start. We will use the timeIntervalSince1970 method provided with the NSDate class to solve this problem.
Add the new properties to ViewController.h for the startTimeSince1970InSeconds that is an integer:
@property int startTimeSince1970InSeconds;
@property int stopwatchStartTime;
Next we move over to ViewController.m and modify our function for startButtonPressed. We want to store our new variables only when we first start our stopwatch. Modify your function to read:
- (IBAction)startButtonPressed:(id)sender {
if (!_currentTimeInSeconds) {
_currentTimeInSeconds = 0;
_stopwatchStartTime = 0;
}
if (!_myTimer) {
_myTimer = [self createTimer];
_startTimeSince1970InSeconds = [[NSDate date] timeIntervalSince1970];
}
}
We store the _startTimeSince1970InSeconds only when the timer is not running, this way the time doesn't change if the user taps the start button while the stopwatch is running. We also initialize _stopwatchStartTime to 0 when we first start. You will now want to go make a similar modification in resetButtonPressed as well as reseting _startTimeSince1970InSeconds when reset is tapped while the stopwatch is running.
- (IBAction)resetButtonPressed:(id)sender {
if (_myTimer) {
[_myTimer invalidate];
_myTimer = [self createTimer];
_startTimeSince1970InSeconds = [[NSDate date] timeIntervalSince1970];
}
_currentTimeInSeconds = 0;
_stopwatchStartTime = 0;
self.stopwatchTimeLabel.text = [self formattedTime:_currentTimeInSeconds];
}
Now we need to store the time in stopwatchStartTime whenever our user taps the stop button. Go make that modification in stopButtonPressed.
- (IBAction)stopButtonPressed:(id)sender {
[_myTimer invalidate];
_stopwatchStartTime = _currentTimeInSeconds;
[self updateTimeInMinutesTextFieldWithCurrentTime];
}
Lastly, we need to change how the stopwatch updates the time whenever the timer fires. Modify your timerTicked function to handle the new calculation by replacing it with:
- (void)timerTicked:(NSTimer *)timer {
int timeInSeconds = [[NSDate date] timeIntervalSince1970] - _startTimeSince1970InSeconds + _stopwatchStartTime;
_currentTimeInSeconds = timeInSeconds;
self.stopwatchTimeLabel.text = [self formattedTime:_currentTimeInSeconds];
}
Notice that we introduced a new variable so we could cast the float that results from timeIntervalSince1970 as an integer so we can conform with how we defined _currentTimeInSeconds.
Build and run your app and see how it works!
You a Better Stopwatch and can use the NSDate class! What's Next?
Thank you for following our walk-through! Hopefully you found it helpful.
In Part 5 we will be continuing to work on our reading rate app. We still have some work to do before it is a full experience.
Download the Project from Today
Share with us your struggles, successes, and stories below!
Limited Time: Pre-order 13 iPhone Courses by Backing Our Kickstarter
We're launching 5 beginner courses and 6 advanced topics in the fall and spring. Pre-order the courses today and get an 80% discount.