RouteRanker was developed using xCode 4 on Mac OS X Lion. Both this operating system and this newest version of xCode are necessary to take advantage of the newest automatic reference counting feature for developing in iOS. RouteRanker is written in Objective-C like all other iPhone applications. All compiling was done with the xCode’s built-in compiler.
Future testing can be done in one of two ways. The first is with xCode’s built-in iPhone simulator; however, this option is not ideal because it does not allow you to use GPS, which is a major component of RouteRanker. The better choice is to put it on a real iPhone. We bought a Developer license from Apple, so RouteRanker can be put on any iPhone that is connected to xCode and signed in with our developer ID. To run the app, a Facebook account is also necessary. Our sharing features can be tested by putting the application on the phones of Facebook friends. After they start tracking paths, they should be registered with the server and appear in the “Share” window.
At its most basic RouteRanker is a tabbed application, meaning that the main navigation is done by selecting one of four tabs at the bottom of the screen. The first tab represents the home screen that is displayed upon signing in to the app. This screen is controlled using a simple view controller. All of the other tabs bring the user to a screen controlled by a navigation controller. Navigation controllers are used in order to efficiently move back and forth between views. They are the most common kind of controller on the iPhone, and are instantly recognizable by their blue navigation bar on the top of the screen. On this bar, we display the title of the screen, a back button on the left, and usually a bar button on the right. The functionality of the button on the right depends on the needs of the view; it is often where we let the user access features of RouteRanker such as renaming a group or getting paths from the server.
You will also notice that occasionally animated transitions happen once a user presses a button in the navigation bar. Over the course of our development we realized that adding buttons and views programmatically was superior to dragging them into the graphical storyboard because it gave us greater flexibility and made the code simpler. Attempting to merge code built on different storyboards created a logistical nightmare and was the source of many of our crashes early on.
Transitions can also occur when a user presses on a cell in a table view controller. This is convenient to take advantage of when a user selects a friend to share or when a user selects a group to edit. Different cells being selected can also lead to different actions and transitions. For example, on the “Compare” screen, selecting the first cell “Make New Group...” prompts the user to name the new group, and then brings the user to a table view of all of their paths. Selecting any of the other group cells simply displays a check mark to show that the cell has been selected. These check marks, either one or two, activate the “stats” button on the top right of the screen. This was one instance where we gained a lot of flexibility by creating views programmatically.
User information is always stored in two places, both on the iPhone in Core Data (the iPhone’s built-in database) and on the server. When the user logs into the app through Facebook their user ID and friends list is pushed to the server for sharing; after that point they are visible to their friends. That information is also stored in the app’s user defaults so that we do not need to query the server each time the app is launched. Upon completion of tracking a route, the route data (including all identification information, points, and annotations) is saved in Core Data for convenient and quick access by the user, and then written to the server for sharing. The list of friends displayed in the “Share” tab is populated by filtering the user’s Facebook friends by the list of users on the server. When a user selects a route to share with a friend, that information is sent to the server, where the route is added to the data for the friend selected. The friend can access this route by selecting the “Get Paths” button on the “Share” tab. When the “Get Paths” button is selected a request is sent to the database. This request returns an array of routes shared with that user, the routes’ owners, and the associated route IDs. Information from these routes is not added to Core Data until the user selects a route and presses the “Download” right navigation button. This way, a user cannot overload a friend’s phone through excessive sharing. Data is only added to a user’s phone when he or she decides.
All of the tables displaying a user’s routes are populated from information from Core Data rather than the database. Routes are relatively data intensive, so fetching from the server is unwieldy. Core Data offers the most logical place to store this information, offering the perfect balance of speed and memory capacity. It also allows users to access all their routes anytime they have their phone, regardless of their data coverage.
We set up a server on Amazon Web Services in which we installed the Bottle framework for Python and mongoDB. We developed a Python RestAPI that is always listening for calls from iPhone devices. Though mongoDB does not explicitly create a table (it’s a schema-less database), we have three collections:
The information is sent to the server as a string with each field separated by commas/hashes (different fields separated by hashes, and each characteristic within the same field such as longitude and latitude are separated by commas). When a user pulls information from the server, this is retrieved as a string with JSON format. To deal with this data, we used the SBJson library for Objective C.
The security of the server is based on hashes. Every time that a user wants to pull a path from the server, we check that the id of that path is in the list of shared paths with that user, and that the hash sent by the user is equal to the one stored in the server. To share a path, we only check the hash of the user (the login is controlled by the Facebook API).