I've been developing an iPhone app since September 2014, almost a year now, for tracking workouts. It might seem like another health app in a genre already thoroughly crowded, but this app has a singular focus: to make entering sets (reps, times etc.) as quick and painless as possible. Calorie tracking is left to apps like My Fitness Pal. 'Propping' workout mates and bragging your one-rep-max is left to Fitocracy. This app is the little notebook and pencil you take to the gym in your back pocket, augmented.
Design & Development
The core of the app is the workouts screen, which presents the user with the details of a workout, and a horizontal display of dots along the top representing workouts in history. The user can swipe horizontally on the workout detail, or on the dots themselves, to page back to previous workouts.
This element allows the user to quickly go back to see a previous workout while they are still entering their current one; this was the number one feature I wanted in a workout app, because it's one of the main reasons people carry a little book and pencil into the gym with them - to see what they did yesterday/last week.
Another of the centrepieces of the app is the timer and stopwatch. The three lines of the stopwatch represent seconds, minutes and hours elapsed. These elements are implemented with a CALayer subclass which automatically animates its start and end angles when they are changed.
The countdown timer has been designed primarily as a 'set' timer. It has a single countdown path, with 4 preset buttons which can be set to any value by long pressing. The user can immediately count down from, say, 1 minute by tapping the 1:00 preset.
The preset time button surrounding the timer are custom drawn with any given text.
Graphing
One of the core UI elements of the app is graphs. There are graphs behind the title of each exercise in a workout to subtly inform the user of their progress. There is a similar graph on the home screen for weight. The weight screen itself has a scrollable graph which re-samples its data as the data points scroll in and out of the viewable area, with variable a goal range.
This was all done with CALayers and masking. Create one layer for your actual graph, one for your goal (with the exact same path as your actual graph path), and a third rectangle path to mask your goal path.
self.goalLayer.path = self.actualGraphPath; self.goalMaskLayer.path = [self rectangleGoalMaskPath].CGPath; self.goalLayer.mask = self.goalMaskLayer;
Theming
I wanted the app to provide theme options for the user. To this end, every single icon and control in the app is drawn in Core Graphics rather than an image, allowing arbitrary tint colour. The actual themes themselves are represented by a Theme object, subclassed from NSManagedObject and persisted in CoreData. A Theme object holds a number of UIColor objects representing colours of various classes of components in the UI; nav bar background/foreground, main background/foreground, text colour, control colour, graphing background/foreground/goal, workout history dots etc. When a theme is chosen by the user, a notification is posted; view controllers inherit from a base view controller, which responds to this notification and calls a method to update the appearance of the view controllers view hierarchy.
To give the use an idea of what theme they are selecting, I use the concept of 'outfits' - (an admittedly cheesy metaphor which fits with the premiss of the app), these are little round icons which represent the theme. These are shown in a grid for the user to peruse, and are drawn in CoreGraphics rather than pre-rendered so that I can create themes on the fly using the app's built-in theme creator, and have them upload to my server for users to enjoy.
Backend/Syncing
I use my old iPhone at the gym, which doesn't have cellular access anymore. I wanted to be able to track a workout on my non-cellular phone at the gym and when I get home have it sync to my main phone.
I decided to go with Azure Mobile Services for the backend. I experimented with Facebook's Parse but came to feel (perhaps irrationally) that I don't trust Facebook's support for this framework to the extent that I'll make Parse the basis for all my persisted objects. In my company we run some of our services on Azure, and it is generally well endorsed by those who implement those services.
The way the Mobile Services works is that you can query and insert into tables directly from your Objective-C / Swift code with convenience methods built in to the Mobile Services SDK. This is fine for simple non-relational data (like saving Theme object representations for example). For more complex data, like saving a workout with its related exercises and metrics, there is the ability to script your own API.
You can do all of this through the Azure web UI, but this quickly gets painful. Set up source control and you can edit and push your scripts locally. This brings me to the one thing which makes working with Azure a bit of a pain for a serious service - there is no simple way to set up a mirror of your environment for staging purposes. You basically have to create an entire second service and copy across all your tables and scripts. Once that's done, you can then have two git remotes (staging/production) for your two environments, but as far as I see there is no way to mirror your database schema to your second service.
Status of Forge
It's the beginning of November now, over one year since I started developing Forge in my free time, and I'm happy to say the app has been submitted and is waiting for review on the AppStore.