This is the second and final part of the Run-Tracking tutorial.
In first part, you created an app that:
Use Core Location to track your route.
Continually maps your path and reports your average pace for your run.
Shows the map for yout route when the run is complete, the map line with mutilcolor to show your speed.
This app is great for recording and displaying data, but you may need a little more of a nudge than a pretty
map can provide.
In this part, you'll complete the app with a badge system that embodies the concept that fitness is a fun
and progress-based achievement. Here's how it works:
A list maps out checkpoints of increasing distance to motivate the user.
Seliver and gold versions of badge are awarded for reaching the checkpoint again at a proportionally fast speed.
I knowed you have downloadd Run_tracking resource in part one
and added it to your project. Notice that pack contains a file named badges.txt which is a large JSON array of badge objects
Each badge object contains:
Information about the badge
The distance in meters to archive this bedge
The filename of the corresponding image in the resource pack
So the first task is parse the JSON text into an array of object.
Select File/New/File and the iOS/CocoaTouch/Objective-C class to create a class Badge that extens NSObject.
Then edit the Badge.h look like this:
Now you have your badge object, and it's time to parse the source JSON. Create a new class BadgeController
extending NSObject, and edit the header as follow:
This class will have a single instance, created once and accessed with defauleController. Open BadgeController.m.
Replace the file contenst with the following code:
There are three methods here that all work together.
defaultController is publicly accessible, delivers single instance of the controller, and make sure
the parsing operation happens only once.
badgeArray extracts an array from the text file and creates an object from each element of the array.
badgeForDictionary mapping the JSON key to the Badge object.
The Badge StoryBoards
Open Main.storyboard, drag a new table view controller onto the storyboard and control-drag the Badge button on
the Home screen to create push segue.
Next, select the table view inside that the table view controller you just added and open the size inspector.
increase the Row Height of the table to 80 points. Then select to prototype cell in the table view and open
the attributes inspector. Set the style to custom and identifier to BadgeCell.
Inside the cell, set the background with black color and add a large UIImageView on the left side. Add two small
UIImageView with spaceship-gold and spaceship-silver assets just to its right. Then add two UILabel.
Each badge will occupy one of these cells, with its image on the left and a description of when you can earned the
badge, or what you need to earn the badge.
Next, drag a new view controller (normal one, not table view controller this time) onto the storyboard. Then control-drag
from the table view cell in the table view controller you just added to this new view controller, and select
push selection segue.
Then make this view controller look like this:
On this view controller you'll see:
A large UIImageView to show off the badge image
A small UIButton on top of the UIImageView, using the info image as background
A UILabel for the badge name
A UILabel for the badge distance
A UILabel for date that the badge was earned
A UILabel for the best average pace for this distance
A UILabel for the date that the user earned the Silver version of the badge
The same for the Gold version of the badge
Two small UIImageView whith the spaceship-silver and spaceship-gold assets
Earning The Badge
Now, you need a object to store when the badge was earned. This object will associate a Badge with the
various Run Object.
Click File\New\File. Select iOS\Cocoa Touch\Objective-C class. Call the calss BadgeEarnStatus, extending NSObject,
and save the file. Then open BadgeReanStatus.h and replace its contens with the following:
Then open the BadgeEarnStatus.m add the following imports at the top of the file:
Now that you can associate a Badge with a Run. Open BadgeController.h and add the following constants at the top
of the file:
Then add the following method to the intreface:
Open BadgeController.m and add the following imports and constant definitions at the top of the file:
Then, add the following method to implementation:
This method compares all user's runs to the distance requirement for each badge, making associations and returning all
the BadgeEarnStatus objects in an array.
Diaplayig The Badges
Now, it's time to bring the badge logic and UI together for the user. let's create two view controllers and one custom
table cell in order to link the storyboard with the badge data.
First, create a new class called BadgeCell extending UITableViewCell. Open the BadgeCell.h make it look like this:
Now, you have a custom cell to use in the table view controller for badges you added earlier.
Next, create a class called BadgeTableViewController extending UITableViewController. Open BadgeTableViewController.h
and make it look like this:
The earnStatusArray will be the result of calling earnStatusesForRuns: in the BadgeController the method you have added
Open BadgeTableViewController.m add the following imports to the top of the file:
Then add the following properties to the class extension category:
There are a few properties that will used throughtout the table view controller.
Find ViewDidLoad: in the implementation and make it look like this:
This set up the properties that you just added. The properties are essentially caches so that each time a new cell
is created you don't need to recreate the required properties over and over.
Next, remove the implementation of the tableView:numberOfRowsInSection: and numberOfSectionsInTableView:.
Then add the following method:
These methods tell the table view how many rows to show and how to set up each row. By the way the cell onely can be selected
if the badge has been earned, through the use of userInteractionEnabled.
Now you need to make the badges table view controller have some data to work with. Open HomeViewController.m
and add these imports at the top of the file:
Then add this property to the class extension category:
Now add the following method:
This will have the effect of refreshing the run array every time the view controller appears.
It does this using Core Data fetch to fetch all the runs sorted by timestamp.
Finally, add to prepareForSegue:sender: so it look like this:
Now, it's time to connect all your outlets in the storyboard. Open Main.storyboard and do following:
Set the class of BadgeCell and BadgesTableViewController
Connect outlets of BadgeCell:nameLabel, descLabel, badgeImageView, silverImageView and goldImageView
Build & Run and you can check out your new badges.
Show The Detail Data
The last view controller for this app is the one thas shows the detail of a badge. Create a new class named
BadgeDetailViewController and extending from UIViewCOntroller. Open BadgeDetailViewController.h and replace
its conents with the following:
Then open BadgeDetailViewController.m. Add the following imports at the top of the file:
And add the following properties to the calss extension category:
Now find the ViewDidLoad and make it look like this:
This code sets up the badge image and puts all the data about the badge earning into the labels.
Finally, add the following method:
This will be invoked when the button is pressed. It shows a pop-up whith the badge's information.
Now you need to open Main.storyboard and make the following connections:
Set the BadgeDetailsViewController class
Connect the outlets of BadgeDetailsViewController:badgeImageView,bestLabel,distanceLabel,
earnedLabel,goldImageView,goldLabel,nameLabel,silverImageLabel and silverLabel
The received action infoButtonPressed: to BadgeDetailsView
Now build & Run and check your new badge detail:
Along with you devoted to badges, you need to go back through the UI of the existing app and update it to
incorporate the badges.
Open Main.storyboard and find New Run view controller. Add a UIImageView and UILabel to its view. It'll
look like this:
Before you an hook up the UI, you need to add a couple methods to BadgeController to detemine
which badge is best for a certain distance, and which one is coming up next.
Open BadgeController.h and add the following method:
Also add this line above the interface, just below the imports:
Now open BadgeController.m and implement those methods like this:
bestBadgeForDistance: The badge that was last won
nextBadgeForDistance: The badge that is next to be won
Now Open NewRunViewController.m and add the following imports at the top of the file:
And add three properties to the class extension category:
Then find viewWillAppear: and add the following code at end of the method:
Then fine startPressed: and add following code at end of the method:
This ensure that the badge label and badge image show up when the run starts.
Now fine eachSecond and add the following code at end of the method:
This make sure nextBadgeLabel is always up-to-date.
And then add this new method:
Maybe you have notice that you haven't implemented playSuccessSound. Let's add this method:
This plays the success sound, but it also vibrates the phone using the system vibrate sound ID. It also helps to
vibrate the phone in case the user is running in a noisy location.
Open Main.storyboard and find New Run View Controller. Connect the IBOutlets for nextBadgeLabel and nextBadgeImageView
Then Build & Run:
Add Space Mode
Open Main.storyboard and find Run Detail View Controller. Add a UIImageView with the same frame as the exiting
MKMapView and set Hidden in the attributes inspector. Then add a UIButton with the info image on it, and a UISwitch with an explanatory UILabel above it.
The UI should look like this:
Open DetailViewController.m and add the following imports to the top of the file:
Next, add two properties to the class extension category:
Then add the following code to end of configureView:
This set up the badge image view with the image for the badge that was last earned.
Now add the following method:
This will be fired when the switch is toggled.
And finally, add the following method:
This will be fired when the info button is pressed.
Now open Main.storyboard and find the Detail View Controller. Connect badgeImageView, infoButton,
displayModeToggled: and infoButtonPressed to the view you just added. Then Build & Run:
Mapping In Your Town
The post-run map alreadly helps you remember your route, and ever identify specific areas where your speed was lower.
Another helpful feature that would be nice to add is to note when you pass each badge checkpoint, so you can divide
up your run.
Annotations are how map view can display point like this.
So you'll begin by arranging the badge data into array of objects to conforming to MKAnnotation. Then you'll use the
MKMapViewDelegate method mapView:viewForAnnotation: to translte that data into MKAnnotationViews.
Create a new class called BadgeAnnotation which extends MKPointAnnotation. Then open BadgeAnnotation.h and replace
its contents with following code:
Then Open BadgeController.h and add this method declaration to the interface:
Add this line under the imports in the same file:
Next, open BadgeController.m and add the following imports to the top of the file:
Then add the following method to the implementation:
This method loops over all the location point in the run and keeps a cumulative distance for the run.
When the cumulatview distance passes the next badge's threshold, a BadgeAnnotation is created.
Open DetailViewController.m and add this import to the top of the file:
Then add the following method:
This is part of MKMapViewDelegate protocol.
Then find the loadMap and add the following line of code just underneath the call do addOverlays::
Now you can look at your map ater a run, and see all the dots that mean you passed a checkpoint.
Build & Run the app, start and finish a run, and click Save. The map will now have annotations for each
badge earned. Cilck one, and you can see its name, picture and distance.