Today I am launching Runtime, the new app I built for simply tracking where I run on iOS 7. I'm really excited that its finished and now I'm looking forward to writing about how I built it. This post will focus on the origin of the product, some of the decisions I made along the way, and give an overview of the tools used.
Runtime is not my first fitness app for iOS. I used the fitness genre while originally learning iOS development. I continued using the original app I created myself. After installing iOS 7 at WWDC I fired the app up, and to no surprise it didn't work at all. The app was originally written to support iPhone OS 3, and though optimized for iPhone 5 and iOS 6 it was still not fully functional on iOS 7.
After using iOS 7 for a few days it was clear that simply stabilizing the app wouldn't cut it. The app and its basic interface were really showing their age. If I wanted the app to be something I was proud of I would need to start over and imagine a completely new product.
I wanted to reuse as much underlying code as possible so that I could spend most of my time building the new user interface and new features I wanted to include. Because of that, deciding what to keep wasn't difficult. I'd recently updated the original app to use Core Data, so all of that stack came over. In fact, almost 100% of the model is reused between the original app and Runtime. At the controller layer I brought over a few key pieces that I knew would be helpful. The UI was where the big divergence was. I brought over some of the views as starting points or if I knew they would be helpful, but overall the UI is where I chose to start from scratch.
The harder part was deciding what to leave behind. The original app supported more than just run tracking. You could use it as a log at the gym, or for tracking your body weight. In the end I had to decide what I wanted to focus on. I had a vision for how I wanted run tracking to work on the iPhone. I didn't feel nearly as passionate about any of the other features. Because of that, I made the difficult decision to cut them so that I could focus Runtime on the area I felt most passionate about.
So I hit Shift-Cmd-N and created a new project. I started from a new project because I intended to use CocoaPods to manage my dependencies. Bolting that on to the existing project would have been problematic. I've been extremely happy using CocoaPods for the past year in other projects, and I love bringing it to my own project.
I also decided to switch from using SVN to manage the project to using Git for the new project. Instead of migrating the old SVN repo I just switched to a new Git repo for the new project. The first commit to that repo was September 29th and there have been 130 commits since then.
The decision to build Runtime was really solidified when I started think about how the user interface could embody the principles of iOS 7: Clarity, Deference, and Depth. The principle that speaks most to me is deference. I love the idea of the app and its interface deferring to the user's content. So when you go for a run at your favorite trail or track, what is your content? I've been using an app to track where I run for over three years, and I always love going back to it to see where I ran, or hiked, jogged, etc. I spend a lot of time hiking, and I have some routes recorded from Big Bend, Colorado, and most recently from the John Muir Trail in California. So your content is actually the places that you go, and the routes that you take when you run there. Once I realized that I decided to center the user interface around providing the user with the best experience possible for saving and viewing that content.
I spent a lot of time making sure that the app could produce high quality map images for the places and routes that a user records. There are a lot of challenges here. Try adding a few dozen MKMapView's to a table view and see how scrolling performs. There needs to be an efficient way to snapshot and cache these map images without trying to render them on the fly. Introducing MKMapSnapshotter - a class for creating map image snapshots introduced in iOS 7. This method of creating map snapshots works great, especially with iOS 7's exciting new 3D map features.
But it didn't quite suit my needs. There currently isn't a way to include overlay's in the snapshot image (rdar: 15390104). I wanted to show the user's route on the map image, so this meant I had to write my own map image renderer. I spent a lot of time making this performant and reliable, and may eventually release it as open source if its something people would find useful.
I really believe that technology is there to enable the user experience. In this case though, the user experience I wanted to create enabled me to try out some really amazing technology :) UICollectionView became the heart and soul of Runtime from day 1. I wanted the core UI to be a collection of places and routes, which would be represented by map image thumbnails. UICollectionView has been around for a while now but its even better in iOS 7 with the new transition APIs. Building the transition between places and routes couldn't have been easier:
routesViewController.useLayoutToLayoutNavigationTransitions = YES;
I love working with transitions and so I was very excited about that as well as the new animated and interactive view controller transitions. Writing about these will easily be another whole post, but for now I'll say that my experience using these has been extremely rewarding and that they were a crucial part of creating the new experience for Runtime. The transitions from the routes screen and the route detail screen are all done using the animated and interactive custom view controller transitions.
I was still in the early concept phase of Runtime when the iPhone 5s was announced. One of the things that sold me on buying the 5s was the fact that I was already thinking about building Runtime. The M7 is such an awesome addition to the platform for developers. I've been using it for a while and so far I think that both the activity tracking and step counting features are very accurate. They have no noticeable affect on performance for me either.
And as a bonus, their APIs are incredibly easy to use. In one of my next posts I'll be going into more detail about how to use these in your apps. The key starting points are the CMStepCounter and the CMMotionActivityManager. For now, the image below is an example of how I am using both of them. The yellow portions of the map represent walking, while the orange portions represent running.
Deferred Location Updates
One of the least known features of iOS 6 and the iPhone 5 is Deferred Location Updates. The feature is also available on the iPhone 5c and 5s. The feature got about a 30 second shout out in this year's Core Location session at WWDC, and a slight honorable mention in the Core Location docs. Deferred Location Updates is a way for the system to deliver location updates in a batch instead of continuously. It uses special hardware to store coordinate sets in a buffer, spin down the A6 or A7, and spin it back up periodically to deliver some updates before putting it back to sleep.
According to Apple this can result in a roughly 40% power savings over long periods of activity. Thats a pretty big deal! In practice its likely going to be less savings than that, since the phone's processor won't shut down if you're listening to music or using other apps in the background. I did get a chance to use it this weekend though. On a 3 hour hike with my phone sitting in my backpack I noticed a much lower power drain than I used to before. For how useful this app is for hiking long distance I am really glad I was able to support this feature!
One of the hardest things about shipping Runtime was choosing a name. I struggled a lot with this. There's all sorts of descriptive names out there, like Running Log, Run Tracker, Jaunt Journal. I really wanted something simple, ideally just a single word. I even thought of Trails, which is actually the name of a very good hiking app I've used. The original name I settled on was Runs. I liked that name because it was simple and accurately described what the app was used for. Thankfully, I embarked on a bit of user testing, and the feedback I got on the name was universal. Runs doesn't make people think of running, it makes them think about sitting on a toilet. So I went back to the drawing board and came back with Runtime. I love the new name both for its simplicity, and its subtle utility as a programming joke :) Those that have read this far will likely get the reference. Thanks for reading and thanks for your interest in Runtime. Check it out on the App Store!