TLDR; We're creating an indoor cycling app for turbo trainers and want to enable users to create workouts on small screens. I was looking for a way to avoid creating blocks, entering their duration, and setting their intensity. Scrolling should be avoided as much as possible. I know that workouts are often described in text – so let's take this and convert it into a workout. It's fast and precise and requires only a keyboard.
The classic way
At first I took a look at existing solutions as I'm not the first one solving this problem. :)
The process is alway the same: a workout consists of an array of blocks. Each block has a duration and an intensity relative to your threshold power. The sum of the individual times gives the total duration.
You add blocks, define the values and save it. Many of the existing solutions use predefined blocks for different intensity zones and some special blocks for warm up and cool down.
I know some platforms where you can do this, but most of them are build for desktop where you have an exact navigation pointer and a lot of screen estate. You can either drag and drop some existing blocks and adjust dem individually or you have a table where you enter durations and start/end intensity values. The latter one sounded like a good start but the UX is a little questionable. Spreadsheet editing on a phone ... fiddly! Another disadvantage is that the result is only visual. My target format is MRC which also has a field for description. So I would end up creating the blocks and writing their description in plain text which is kind of redundant.
The idea
Given that the user interface must be compact, the description should be human readable, the result should be exact and immediately visible I came up with the idea to define the workout via ... language. As we have a very specific domain this solution is very close because this is the classic use of a DSL (domain specific language).
There should be a textfield where you enter the description and a preview that displays the blocks and calculates the duration. The preview should render as you type. So of course this sounds like SwiftUI. For the parser itself I used after some minutes of research a couple of regexes because I didn't want to use Scanner and tokenizers like Mustard or lexers I found were all a little out of date or didn't fit my needs.
The language
The aim was to keep it as short and simple as possible but remaining readability. All power values have to be in percent of power threshold and duration should be in seconds or minutes. These are the constructs for different types of blocks:
Boundary blocks for warm up and cool down
warm up for 5 minutes
will create a ramp block starting at 20% and end at 50% power with a duration of 5 minutes.
cool down for 5 minutes
will create a ramp block starting at 50% and end at 20% power with a duration of 5 minutes.
Simple block with constant power
75% for 180 seconds
will create a block with a constant power of 75% and a duration of 3 minutes.
Ramps with increasing/declining power
from 35% to 100% for 120 seconds
will create a ramp block starting at 35% and end at 100% power with a duration of 2 minutes.
Loops to not repeat myself
This is the most complex construct but removes a lot of copy and paste as most workouts follow the rule that a set of blocks gets repeated with for a number of times with a break at a lower power between them.
repeat 10 times
80% for 30 seconds
35% for 3 minutes
with a break at 40% for 5 minutes
Repeats the two blocks for 10 times and creates a pause of 5 minutes at 40% power between them.
The iOS App
The app was the easiest part as I had already a workout structure and a chart that takes a view model. I just had to split the lines, iterate over them and create the blocks. After adding a stack for the loops I was done. Some styling later this is what I ended up with:
Export
My workout structure can already be exported as MRC so I just could save it to a temp directory and hand it over to other apps consuming the format. Like The Surge :)
Conclusion
This went really well. In only a couple of hours (after thinking and failing for weeks) I had this and think that it is cool. This will make it into production … after a rewrite with a robust parser instead of Regex.