Side Project: Stoutness
This is part of a series of blog posts in which I showcase some of the side projects I work on for my own use. As with all of my side projects, I’m not focused on perfect code or UI; it just needs to run!
Over the years I’ve been slowly tinkering with an idea I liked to call “The Morning Briefing”, a simple way to give me the information I want in the mornings such as the news, weather, and a list of my upcoming tasks. Originally this would have been read to me by Siri1 but in mid-2020 I decided instead it would be more fun to have it as a printed page; a personalised newspaper just for me2. It would have consisted of weather icons for the next 7 days, a list of my tasks I could physically check off, and then maybe some personal data from Health such as Apple Watch rings, heart rate data, and sleep averages.
I did make a start on this idea but then late last year my chiropractor told me that I desperately needed to improve my activity level. I also needed to perform the stretches I’d been assigned back in 2017 every day as I’d fallen out of the habit. The video I use for my stretches is by Straighten Up UK and whilst it’s very good there are several bits that can be fast forwarded through once you know the routine. It was also affecting my recommended videos on YouTube as I’d watch it every morning which meant YouTube kept assuming I wanted more and more stretch videos. Eventually I decided that I’d just download the video, remove the extraneous bits, and put it in a basic tvOS app. I then realised that my Morning Briefing idea might work better if it was paired along with the stretching video and thus Stoutness was born.
Tangent: Name and App Icon
As any good developer knows, half the battle is what to name your side project. Originally this was "Morning Briefing" and then became "Hummingbird" after one of the sillier stretches which you'll see shortly. The final name was eventually hit on when my wife remarked (rather cruelly) that I looked like Winnie-the-Pooh doing his stoutness exercise.
One of my favourite things about working on tvOS is the way in which parallax is used to convey focus. Naturally I wanted to have a nice icon if I was going to use it every day and so I decided to take Pooh Bear and let him move around a little. The icon is made of three layers; Pooh on top, the room (with a hole where the mirror is) in the middle, and then the reflection at the back. The result is that both of the Pooh's move opposingly as the icon is highlighted. The whole thing was taken from a very low-res screenshot and then I made judicious use of the "content aware fill" tool in Photoshop to fill in the extra bits of image I'd need such as the space behind Pooh and extending the reflection cut out. You can download a zip of the three individual layers if you're curious to see exactly how it works.
As with all of my side projects, I generally like to use them as an excuse to learn some new code technique. In this case, I mainly focused on how to mix-and-match SwiftUI with UIKit (as that seems like it may prove useful in the coming years) but I also dabbled in some Combine and with using Swift Package Manager over CocoaPods for the limited number of dependencies I use. In the end, I also needed to dabble with AppleScript, Shortcuts, and writing a couple of iOS and macOS apps to help power everything…
On opening the app the current date is shown along with a loading spinner whilst the five different data sources are called and cached; two of my own APIs, YouTube, Pocket, and OpenWeather. Only once they are all loaded does the “Get Started” button appear.
The weather icons are displayed as soon as the OpenWeather API response is received and show the weather for the next 7 days along with the high and low temperatures. This entire row is powered by SwiftUI with each day rendered like this:
I love how quick and easy it is to prototype something with SwiftUI and with the new Apple Silicon Macs it’s actually feasible to use the realtime preview in Xcode. The icons for each weather type are pulled from SF Symbols and you may notice I had to lock the
VStack they appear in to 120pt high with a
Spacer underneath; this is because the icons are all different sizes and so
cloud would be misaligned when placed next to
In my original idea for “Morning Briefing” I had wanted to get the news headlines along with an excerpt. This is relatively easy to do via something like BBC News with screenscraping but the recent pandemic meant that nearly all of the “top read” stories were pandemic related which I didn’t really care to read about. I have a subscription to The Times so thought about scraping that site somehow but there was no easy way to get the same content as the newspaper so it ended up being whatever was breaking right now which I also didn’t want3. Perhaps I could hook into Apple News now that I’ve got a subscription with Apple One? Nope, Apple News has no APIs or SDK and one look at the network requests with Charles Proxy made me think I didn’t want to go down that rabbit hole. In the end, I opted for a much simpler solution of having a carousel of the front pages of today’s newspapers. This lets me see the main topics of the day without getting bogged down in article excerpts which are often a bit click-baity to get you to the actual article.
I tried a few ways to get front pages (originally from the BBC and Sky News websites) but in the end I wrote a very simple PHP script to scrape tomorrowspapers.co.uk every few hours:
The front pages themselves are image views within a
UIPageViewController. This has the nice side benefit that they are automatically placed into an endless carousel that can be swiped through with the Siri Remote. I randomise the pages each day so that I see each newspaper in a different order.
Tangent: Page Layout
There are 12 stretches in my chiropractic video which I've trimmed from the original downloaded video and saved as individual files. I then typed up the narration for each exercise and placed it in a JSON file.
The newspaper page and all the subsequent pages are all the same
UIViewController which use the data in the JSON file to display the appropriate video, stretch name, and instructions. They then load a child view controller on the right hand side of the page; this was a
UIPageViewController for the newspapers but is a
UIHostingController with a SwiftUI view in it for all of the following stretches.
I’ve been a long time user of Things, the task manager based on the Getting Things Done methodology. I have several repeating tasks in intervals along with ad hoc tasks I create throughout the day, all sorted into areas and projects prefixed with an emoji so they don’t look quite so dull. Unfortunately, there is no online component with Things4 and thus no public API as there is with something like Todoist. Whilst there is some support for Siri Shortcuts they are for opening the app rather than extracting data out of it. However, the macOS app does have comprehensive support for AppleScript and so that offered me a way to get at the data:
There’s quite a lot there but essentially it boils down to going through my Today list and sorting each task by its project. These all then get added to a plist (as easier to deal with in AppleScript than JSON) as an array of dictionaries containing the task name and project name which is then saved to my Dropbox where a PHP script on my server can access it and return it through my own API.
One habit I’ve gotten into over the years is to create a number of short tasks that need to be done at some point and then tagging them “One A Day” with the aim being to do one of them every day. In this way, small jobs around the house or minor tweaks to projects slowly get done over time rather than lingering around. To help with that, the script above will find all of my tasks tagged in such a way, pick one at random, and then move it to Today before the list is exported.
On the app side, this plist is fetched and then parsed in order to make it more palatable to the app by grouping the tasks into their projects and fixing a few minor issues i.e. tasks in Things marked as “This Evening” still show up in the AppleScript as their project name so I rename their project to “🌘 This Evening” mimicking the waning crescent moon icon Things uses in its apps. I could likely do this within the AppleScript file but it’s just easier to do it in Swift!
In an ideal world this script would run automatically first thing in the morning so everything is synced up when I open the Apple TV app. Unfortunately this isn’t possible for me at the moment as I’m exclusively using an Apple Silicon MacBook Pro and I don’t leave it running continuously as I used to with my Mac Pro. For now, I need to run the script manually each morning (which is as simple as double clicking an exported app from Script Editor) but this will change once the Apple Silicon desktop machines launch!
Another habit I’ve gotten into recently is scheduling my week in advance. I do this for my client work but also for things like what exercises I’m going to do or what meals I’m going to eat5. Whilst I could have just typed it into a database to get it out of the API easily, it isn’t much fun manually entering data into an SQL client. Instead, I decided to use the calendar app on my mac and sync the data using Shortcuts on iOS. This is far easier as I can quickly type up my schedule on a Sunday and then sync it each day prior to starting the app.
Shortcuts née Workflow is a powerful system app that allows you to join data from various apps together. I use it extensively for this app, mostly to get otherwise inaccessible data out of my iPhone and into my database so my API can send it to the app. For example:
- Fetching my calendar entries for the day and formatting them into a JSON array
- Retrieving my exercise minutes and step count from a custom HealthKit app I wrote
- Getting the amount of water I drank yesterday from Health
- Fetching my sleep stats from the AutoSleep app
All of this data can then be packaged up in JSON and posted to my server using the poorly named "Get contents of" network action.
The only downside is that I do have to trigger this manually as no app, not even Shortcuts, can access your Health database when your iPhone is locked so a shortcut set to run at a specific time won't work unless I'm using my phone. This hasn't proved too annoying though as I'm now in the habit of running the shortcut directly from a widget on my home screen before I launch the Apple TV app.
I divide the day up into five sections; Anytime (all-day tasks), Morning (before midday), Lunchtime (midday - 2pm), Afternoon (2pm - 5pm), and Evening (after 5pm). If there are items at a specific time then that time is displayed but mostly items will be from my Schedule calendar which is comprised of tasks tagged in a specific way. For example, for dinner I will create an all-day task named “[food:dinner] Penne Bolognese” and the app will know to use the correct prefix and colour. Once again, SwiftUI becomes a joy to use for short interfaces like this:
The entire interface took less than 5 minutes to create and avoided the usual need to create either a reusable cell in a table view or a custom view to be added to a stack view dynamically.
Getting more sleep is usually the simplest way you can improve your life. If you’re getting less than 8 hours a day then sleeping more will improve your mood, cognition, memory, and help with weight loss and food cravings. I used to be terrible for sleep, typically getting 5 hours or less a day, but a concerted effort this year had me at an 8 hour average for most of January and February.
I use the excellent AutoSleep app on Apple Watch to track my sleep. The counterpart iOS app can reply to a Siri Shortcut by pasting a dictionary of your sleep data to the clipboard making it trivial to export and use as part of a workflow (although it also saves to HealthKit so you could just export it from there as well depending on what stats you are after).
The interface above is about to become fairly familiar as I use it for a number of metrics within the app. It comprises of 3 rings in an Apple Activity style giving me the sleep duration for last night, the nightly average over the last week, and the nightly average for the current year. Each ring is aiming to hit the 8 hour target and I write the duration as both time and percentage within each ring.
The rings themselves are provided by MKRingProgressView which are Swift views for use within UIKit apps. To get them to work in SwiftUI was relatively straightforward requiring only that I write a small class conform to
Each ring can then be created in SwiftUI as simply as:
The only issue with this is that it's slow, especially on the Apple TV hardware which is several chip cycles behind modern iPhones. It may be the
MKRingProgressView class itself is quite heavy going but I get the feeling translating it to work with SwiftUI and then pushing the whole SwiftUI view back to UIKit via
UIHostingController might also be adding some lag. Overall it isn't a problem but it does mean that the view takes a half second to load.
As part of a concerted effort to improve my health, the amount of alcohol I was drinking had to come down fairly significantly. I’ve tried many habit tracking apps in the past mostly in the form of maintaining a chain; the problem I have with them is that once the chain is broken I tend to go all out (i.e. if I’m going to get a black mark to say “drank alcohol” for having a glass of wine I may as well have a bottle of wine). To remedy that I take the idea of chaining but also pair it with the government guidelines for alcohol consumption which is 14 units a week for men.
The data for this is manually input into my SQL database for now but I have other apps I’m working on that interact with those tables. For now, I add what I’ve drank and how many units along with a date and the API then returns the data you see above; how many units I drank yesterday, how many days since I last had something alcoholic (if yesterday was 0 units), the number of units I’ve drank in the last 7 days, and a weekly average for the past 3 months. This differs slightly from the other ring-based metrics in the app as rather than a daily average over 7 days I needed to fetch a cumulative sum to ensure I wasn’t going over 14 units in a 7 day period. The weekly average is also interesting as I needed to go through each Monday-Sunday period of the past 3 months and get a cumulative total before averaging that.
This system has worked very well for me with the result being an average of 1 glass of wine and 1 beer a week which is down significantly from what was likely around 4 bottles of wine and several beers a week. I also had a dry spell of over 2 weeks which is likely the longest duration in around 10 years. I have a lot more thoughts around alcohol tracking and logging which I’ll come back to in a future side project.
The one update I might make to this page is to show how much water I’m drinking a day. I track my water intake via WaterMinder and already sync the data to my server using the Shortcut I’ve mentioned previously. Originally I had a whole page for showing this data but I dropped it in favour of some more important metrics as I found my water levels were pretty consistent once I’d gotten into the habit of drinking more. It looks like it’s not something I need to keep an eye on quite so much but if I do bring it back I think it will be on this page.
Along with doing my morning stretches, my chiropractor was also very adamant that I needed to increase my physical exercise as my step count was a rather woeful average of 6000 steps per day in 20206. To that end I’ve started running again on my treadmill and going for multiple walks each day.
Whilst the data above is rendered in much the same way as the sleep data, getting it is far tricker than you might think due to the complication of having an Apple Watch and an iPhone. If you use Shortcuts to fetch your data from Health, then it isn’t possible to deduplicate properly and so you’ll end up with a total number of steps from both your Apple Watch and iPhone which is bad if you ever walk around with both devices at the same time which I obviously do. You can limit the fetch to a single device but then you run into problems as you won’t be recording any steps whilst your watch is charging for instance. I don’t know how this error has been allowed to continue for so long in Shortcuts but the solution is rather simple for iOS apps: you use a
HKStatisticsQuery along with the
.separateBySource options. Unfortunately this meant I needed to write an iOS app to fetch my steps data along with an Intent Extension to allow that to then be exported via Siri Shortcuts.
After opening the app and authorising it for read access on my HealthKit store, it now sits hidden in the App Library and is woken each morning as my sync shortcut requests the data and sends it to my server. It’s a long winded solution but it works!
The exercise view is pretty much identical to the steps view previously only with different data input, colour, and a goal of 30 minutes. Originally I used the native Health actions within Shortcuts to extract the exercise minutes but then I ran into similar duplication issues if I wore my Apple Watch whilst doing a work out on my Peleton bike as that too would add an exercise. To make things simple I added exercise minutes to the same exporter app I mentioned above.
Whilst I’m on my treadmill I’ll typically watch a variety of videos I’ve saved on YouTube. This screen shows me the number of videos left in my “To Watch” playlist along with the total runtime7. I then show the details of five videos; the most recently added, the two oldest, and then two random ones from those that are left. In this way I get to know which videos have been sat a while and that I should really watch whilst also seeing something that is likely more exciting to me right now as I added it most recently. I’m not forced to watch these particular videos but I find I usually do choose them as it’s one less thing to think about when I get onto the treadmill.
Fetching the videos is relatively easy using the YouTube API as a single request can give you all the video details for a single playlist. Unfortunately, and for reasons I don’t understand, YouTube doesn’t provide access to the default “Watch Later” playlist even with an authenticated request so I had to create a public playlist named “To Watch” and then remember to add all of my videos to that. It’s a bit of a pain as I nearly always want to just click the “Watch Later” button on YouTube but I’m slowly getting used to adding to the playlist instead.
In a similar vein to the YouTube page, I do something very similar to show me the articles I currently have to read in Pocket8. I connect directly to the Pocket API and return the number of articles remaining, the estimated reading time of them all, and then 5 articles as a mixture of newest, oldest, and random entries.
I mentioned previously that I like to have a couple of “One A Day” tasks so I can make slow and steady progress on items over time. This is also true of my own personal projects such as this app which will often remain nothing but ideas unless time is carved out to work on them. To help with that, I maintain a list of the projects I’m working on and then have the API pick one at random each day to show me; the idea then is that I will work on that app for at least 18 minutes9 during the day. It’s a system that has worked well for me and gets around the issue of the current favourite project getting all of my attention.
Tangent: Choices Choices
There is a theory that "Overchoice" or "Choice Overload" occurs when we are presented with too many options and that it causes us stress and slows down our process of choosing whilst also sapping our energy reserves. The original studies of this from the '70s have since been called into question but I personally find that oftentimes if I'm given the freedom to do anything I'll end up doing nothing as I can't choose between the numerous options available to me. This is true of what I should watch, read, play, and work on in my spare time. My solution, as briefly alluded to above with YouTube videos and Pocket articles, is to take a subsection and return them randomly so that I don't have to pick.
This is something I also use with my personal projects, books, and games; rather than have all the options available to me, I take the items that are currently active and then the app will return a single result each day. This avoids any decision making on my part as if I want to do some coding I already know what project it will be on or if I'm going to read I know which of my current books it will be. The only rules I set myself are that if I really want to do something else, I must meet the minimum requirement for the day on the original choice first (i.e. lets say I want to work on my Lord of the Rings LCG app but I've been assigned Pocket Rocket, then I work on Pocket Rocket for the 18 minutes to fulfill my daily goal and then I'm free to work on whatever I like).
The one thing I'm particularly mindful of with this approach is that I may need to tweak how random it actually is. With true randomness, I could get the same result every day for a week and generally I'll want to mix things up a bit. It's also the case that I don't necessarily have time for my hobbies every day so I might have 2 days reading the same book and then go 3 days without reading anything; when I get to the 6th day the same book is randomly returned and I've missed out on the other books on the previous days. The only way around this is likely to remove results if my database sees I've spent concurrent days on the same item. I've also started restricting these choices to just 3 active items at a time as otherwise there are a few too many for the randomness to work nicely.
For logging time on projects, I have written a small app named “Time Well Spent” which I’ll be covering in a future side projects article. This data is then stored in my database where my API returns it for rendering the three activity rings in a similar way to the steps or exercise rings covered earlier.
I’m a big believer in the power of Idea Sex, the notion that by juggling several different thoughts together you’ll trigger more creative thinking. One way to do this is to read multiple books at the same time rather than reading in a linear fashion. As you may be able to guess from the tangent box above, I do this with books by having the app tell me what book I’m going to read today from the pool of books I’m currently reading. Beneath that I render the familiar three activity rings based on data I’ve logged via my “Time Well Spent” app and a 30 minute daily goal. I might also add a tally of how many books I’ve completed this year at some point but for now this is working well.
I listen to an audiobook on most of my evening walks and have recently begun tracking that data as well. It’s likely that I may try juggling multiple audiobooks and use a similar system to the above to pick one for me each day.
The final screen was originally intended to be a bit of a sweetener to force me to do my stoutness routine every day; it tells me what video game I can play today. However, the amount of time I’m spending on video games has dropped fairly dramatically this year taken up by an increase in exercise and board games and (soon) a newborn baby. It’s no longer quite the pull it once was but I’ve managed to go through this routine every day so far this year so I’m not too concerned!
I’ve long tracked my gaming hobby as I used to run a website where I’d output my currently logged time and write reviews. I have a dedicated app for this purpose which I’m currently migrating into “Time Well Spent” but I also have some automated scripts to fetch my play time from services such as Steam and Xbox Live. In addition, I have a number of game collections which I can query such as “Currently Playing” and “To Play”. Using these, it was very easy to render the above page showing which game I’m allowed to play today but also giving me a secondary choice should I complete that game10.
I don’t use the activity rings to render the average data as I don’t have a goal for how much time I want to play for whereas steps, exercise, projects, and reading are all things I want to do a certain minimum of (or maximum in the case of alcohol).
As of the time of writing, I’ve spent 24.4 hours working on the Stoutness app from its origins as a printed daily report to the Apple TV app it is now complete with its suite of data providers from AppleScript apps to Shortcut workflows. Since using it daily from the start of the year I’ve had huge improvements in nearly all of the metrics I track; I’m sleeping longer, drinking less, exercising more, and reading more. I’ve also noticed big improvements in my health with my daily stretching leading to more flexibility11 and less aches and my average resting heart rate falling from ~70bpm to ~50bpm. Finally, I’ve lost over 22lbs since using the app which is just over 10% of my starting weight; that isn’t all attributable to this app but it has definitely helped by encouraging me to keep working on the various rings that I see every morning.
With a lot of side project apps I often think about how they can be adapted so others can make use of them but in this case what I have is a highly specialised app bespoke to me… and that’s OK! One of the great joys of being a developer is that you can take seemingly disparate data and merge it together into something just for you. Hopefully this article will inspire you to do likewise and learn some new coding skills whilst building something to help you in your own life.
Originally I would have done this using text-to-voice within an app but obviously it’s far easier to do it with Siri Shortcuts nowadays such as this Morning Routine by MacStories. Also, with HomePod update 14.2 it is now possible to say “What’s my update” and get something similar. ↩︎
I despise rolling news and the culture of “information now” which is why I subscribe to a newspaper. I like to get a clear picture of the days news after the dust has settled rather than the incessant guessing at what is happening, about to happen, and the reaction of what just happened. Don’t get me started on “[public figure] is expected to say” with announcement previews. ↩︎
Well that’s not strictly true. There is a private API as data is synced to Things Cloud but I don’t fancy reverse engineering that! ↩︎
I used a meal planning app for a while but it was too much hassle to peck everything in on an iPad; now I just create an all-day event in a dedicated calendar. Why plan meals? Mostly because I’m terrible with letting food expire especially at the moment where recent food shortages at supermarkets have led to shorter shelf lives on several products. ↩︎
The real average was likely lower as there were 2 weeks in Walt Disney World where I was over 20k steps every day which would skew the averages. ↩︎
Rather than returning something normal like seconds, YouTube returns video durations in a format like PT24M49S which required an extra bit of working around. ↩︎
Obligatory “go check out my Pocket app” comment. ↩︎
18 minutes is a bit specific you might very well think but there’s a (sort of) good reason. My database first started logging games and these were done in a decimal format of hours to one decimal place as that’s how they came from Steam thus all of my gaming time is broken down into 6 minute chunks. When I added other items to the tracking system I kept the same format and this persisted when I started tracking how much time I was spending on personal projects; whilst I’d probably prefer it to be a goal of 15 or 20 minutes a day, it’s 18 so it can be represented cleanly as 0.3 hours in the database. I could make the ring show 20 minutes but then it would always be not quite full or over full and that seems messy. ↩︎
This is something I’ve also thought about adding for books but it’s slightly different as I know how long it will take me to finish a book — you can see how many pages are left — but it isn’t always obvious how much you have left of a game hence the need for a backup if I sit down and finish the current one in short order. ↩︎
At the start of the year I used to struggle to touch my knees with my elbows for the “Twisting Star” exercise as I just couldn’t bend that well; now I can do it with no problems. Hooray for basic levels of fitness! ↩︎