That said, things can always improve. Here are the things I’d love to see come to Apple’s developer tooling in the future.
SF Symbols has been a relevation. A suite of thousands of free-to-use, customisable icons is an enormous time saver for almost every developer. I really like Apple’s approach here of “taking a thing that every developer needs to do and removing some of that burden”.
TipKit is in a similar vein. Presumably, Apple noticed a lot of developers building things like this themselves and decided to offer a first-party solution.
Following that approach, I’d love to see an API for getting localised strings for common words and phrases, so that developers don’t need to translate them. They wouldn’t be able to cover all (or even most) strings, but if they covered common words and phrases that appear in most apps like “Done”, “Cancel”, “Edit”, and so on, it could go quite a long way.
Here’s a crude code sample of how it could look:
HStack {
Button(action: {}) {
Text("Cancel", language: .deviceCurrent)
}
Spacer()
Button(action: {}) {
Text("Done", language: .deviceCurrent)
}
}
Depending on the user’s locale, this could have different results:
It could fail gracefully too. Just like when you try to use an SF Symbol that doesn’t exist, if you try to use localised text for a string that isn’t supported, nothing happens.
Apple’s already done the hard work of localising all these strings in iOS itself, so this would ‘just’ be a matter of exposing them to developers with a new API.
UserDefaults
is a great way to store simple data for your app, like user preferences. However, the data is only stored locally and doesn’t sync across devices.
Apple has a solution for this: iCloud key-value storage. It allows us to sync simple data across devices in a way that’s transparent to the user. No logins or sync buttons, as long as they’re signed into iCloud it Just Works™.
The downside is that we need to manage the syncing ourselves. Recently when developing SalaryPig I wanted to implement this and found it decidedly non-trivial. I eventually figured it out using Zephyr, but this feels like another thing that most app developers will need to tackle if they support multiple devices.
To improve this, Apple could offer convenience APIs to automatically handle this. SwiftUI already has a property wrapper to make any state variable be backed by UserDefaults
:
@AppStorage("Foo") private var foo = 1
It’d be great to have a version of this backed by iCloud key-value storage. It might look like this:
@AppStorage("Foo", synchronized: true) private var foo = 1
On the Swift side without the property wrapper, it could look like this:
UserDefaults.standard.set(1, forKey: "Foo", synchronized: true)
Another improvement to UserDefaults
would be to allow them to sync to watchOS easily. I ran into this issue when developing SalaryPig for watchOS, where I expected that values stored there would be available via an App Group, just as they are for widgets and other iOS extensions.
I did some digging and it seems that in watchOS v1 it did work this way as watch apps were extensions of iPhone apps, but when watchOS apps became fully-fledged apps UserDefaults
was no longer shared between them.
There are plenty of ways around this but all of them require some manual work. Some sort of opt-in automatic syncing between iPhone and watchOS would be really handy.
Or is it forwards compatibility? I’m not sure. Either way, it’d be great if SwiftUI was less linked to the current OS version. SwiftUI improves hugely every year, but many developers can’t use the latest improvements for 1-2 years, depending on the minimum OS version they support.
On the other hand, Jetpack Compose (which is essentially the Android equivalent of SwiftUI) supports versions of Android going back a lot further.
I know very little about how SwiftUI works ‘under the hood’, but if it’s possible to decouple the OS to the runtime it’d be useful for thousands of developers.
Recently I ported SalaryPig to visionOS. I really wanted to make Trevor (the animated piggy bank) 3D, but I found the learning curve of making 3D graphics too steep for a complete novice like me. I might come back to it later, but for now the best solution for me was just to keep Trevor in 2D like on other platforms.
I think there’s an opportunity here for Apple to offer something that can help people build 3D assets easily. I’m thinking of something like SwiftUI’s robust shapes and paths, extended to also support 3D shapes.
SwiftUI’s date picker is really good, and I’d like to see it extended to support a wider range of uses, like selecting a range, and showing multiple months at once. It’s another thing that many apps need to build custom, and first-party support would be really convenient.
Lastly, I’d like the ability to open a WKWebView
from a SwiftUI view. Currently it’s trivial to open something in Safari using a Link
, but for some links it’d be a nicer experience to have it open within the same app. It could look like this:
let appleUrl = URL(string: "https://apple.com")!
// Current behaviour; opens in Safari:
Link("Visit Apple", destination: appleUrl)
// Proposed modifier for opening in a WKWebView:
Link("Visit Apple", destination: appleUrl)
.linkHandler(.inAppBrowser)
// Using it on a link inside Markdown text:
Text("Visit [apple.com](https://apple.com) to learn more")
.linkHandler(.inAppBrowser)
That’s all I have on my mind for now. I love seeing how Apple’s developer platforms evolve each year, and I’m excited to see what’s in store for WWDC 2024.
]]>In December after a career break I started a new job, and after two days I realised I’d made a huge mistake. The company was very corporate and political, and the actual role was quite different from what I’d been told during the hiring process. It’s a fine place to work, but just wasn’t a good fit for me.
The silver lining was that at least I had regular income again. As I sat in dull meeting after dull meeting, I consoled and occupied myself by calculating how much salary I was getting to sit in these meetings. “10 minutes go by – there’s another £3.42 … 8 minutes left – another £2.89 …“.
Off the back of that, I decided to make an app so that I could glance down at my phone or watch any time and track how much I’d earned that day. Sure, I was using it out of frustration at having chosen the wrong company, but I could see lots of reasons people would want to track how much they’re earning.
Thankfully I’ve now left that job for a startup that’s more aligned to me, but not before finishing the app.
SalaryPig is a simple app that does one thing: tracks your salary each day. You just enter your annual salary and your work schedule, and SalaryPig will update a counter each second to show how much you’ve earned that day so far.
Tracking your salary is basically a glorified calculator, so I decided to add some personality by adding Trevor, an animated piggy bank who lives on your phone. He’s awake when you’re working, and outside of working hours he’ll probably be napping (don’t try to wake him up though, seriously).
I’ve never done any real work with graphics before, and building Trevor was loads of fun and a great learning experience. He’s constructed from a bunch of overlapping SwiftUI views and animations, and a lot of trial and error to get him just right.
SalaryPig is free to use, and additional features like widgets and Apple Watch support can be unlocked by subscribing to SalaryPig Pro. I find the Apple Watch complication particularly useful, as I can just look at my wrist throughout the day and see my earnings count up.
The first version of SalaryPig is very much an MVP. Some ideas I have for future updates are:
I wanted to call this app PayPig at first, it’s much snappier and alliterative, but a quick google ruled that out quickly…
]]>In this world it’s normal to have lots of third-party dependencies. In many cases this makes sense. The default front-end environment is quite bare bones, so you’ll need to add any frameworks and tooling you need. A typical front-end setup might use React as a UI framework, TypeScript for type safety, Jest for unit testing, Storybook for a development environment, and various other libraries. Libraries like this tend to be split across multiple packages so that you only download the parts you need, so your project can quickly gain dozens or even hundreds of dependencies.
The web development community moves quickly, with all of these packages getting frequent updates (often weekly). Staying up to date with the latest version of all your dependencies can a big (but necessary) time sink, and it’s common for repositories to fall behind on these updates. This can cause security issues, as well as compatibility ones when libraries you’re using become incompatible with each other.
Another issue with this approach is that it becomes all too easy to add more dependencies that you might not really need. When you already have 80 dependencies, why not add a few more? This is so prevalent that it’s even been parodied with the is-thirteen package, which you can install to check if a number is equal to thirteen.
When I started making apps for iOS I resolved to only use third-party dependencies that I really need, and keep it as close to zero as possible. iOS comes with a lot of tooling already, so a lot of the things I’d need in the JavaScript world can be avoided. If I’m putting my name on something I want to know what every line of code does and not have any ‘mystery meat’ packages where I don’t know what they’re doing behind the scenes. I may have overcorrected and been too militant on not adding dependencies, but I feel I’m at a comfortable place with my dependencies right now.
These are the ones I include in my iOS apps, and why.
I’m a huge fan of TelemetryDeck, a privacy-first tool for analysing user behaviour. The SDK is very lightweight and is pretty much just a wrapper around sending HTTP calls to TelemetryDeck’s servers. It’s a breath of fresh air compared to other solutions for analytics like Firebase which are enormous and could be doing all sorts of things in the background.
I use RevenueCat to handle the in-app purchasing in my apps. I’ve toyed with using StoreKit 2 to handle subscriptions with Apple directly, but RevenueCat feels like it’s worth the small cost. The SDK is lightweight, abstracts away lots of edge cases I’d otherwise need to worry about, and receives frequent updates. Plus, they sent me a t-shirt when I made my first purchase through them, which was nice.
You know those sheets that pop up in a lot of iOS apps when they’ve been updated, like What’s new in Photos? WhatsNewKit is a library for displaying those. I use it in Personal Best to inform users what’s been changed when they open the app after an update. Ordinarily this is something I’d build myself, but I really like how it abstracts away the logic for only showing the sheet to users who haven’t dismissed it already.
Drops is a library for displaying little status messages (something called toasts) to users when something happens in your app. In Personal Best, it’s used to notify users when they get a new achievement. Initially I built this myself but I found it was a little unreliable – for example I couldn’t get it to work correctly when a sheet was being displayed, because the sheet would be at a higher Z index than the notification. In the end, I switched over to Drops, and I’ve not had a single issue with it. I would like to see something like this become part of SwiftUI though to negate the need for such a library.
Are there any other must-haves that you think every project should have? Let me know on Mastodon or email.
]]>If you’d like to catch up on previous years first, check out 2022, 2021, and 2020.
At the end of 2022 I set myself three goals:
Status: Achieved ✅
I didn’t just double my recurring revenue, I increased it 61x 🤩. The two catalysts for this were having a career break (I took five months off from paid work to work on Personal Best full time), and getting featured on the App Store.
Back in September I wrote about this in more detail and set myself a revised goal to hit $1,000 monthly recurring revenue (MRR) by the end of the year. As I write this I’m at $1,279 MRR, so I was able to achieve this goal too.
Status: Missed ❌
Unfortunately I didn’t achieve this, but it was a deliberate decision where I decided to focus on improving other parts of the app instead, and come back to this later.
Status: Missed ❌
I missed this one too. I got really close to having the new app – another type of health tracker – finished, but I decided to spend my time focusing more on growing Personal Best for a while instead. I intend to pick the app back up in the future and release it though, so watch this space.
I made full use of my time away from work this year, and shipped a bunch of new features in Personal Best:
In February I launched a biweekly newsletter to showcase nice design details I found out in the world. I published 19 issues and then decided to put it on hold, because frankly it took a lot of time to maintain and I only got 65 subscribers.
In October I attended SwiftLeeds, my first iOS conference. I learned so much and it was brilliant getting to meet other iOS developers.
Jeroen invited me onto his AppForce1 podcast, which was a really fun experience. I’d love to appear on more podcasts in the future and meet more iOS developers.
Man City won the treble, which wasn’t technically a personal achievement but it felt really great. 🩵
As you’d expect with the increase in revenue, Personal Best was downloaded much more in 2023 than in previous years. This year, Personal Best got 87,000 downloads, compared to around 2,000 last year (43x more).
Taylor’s Version, a utility app for swapping Taylor Swift songs in your Spotify playlists with the re-recorded versions, also got a bump this year, going from 2,500 downloads in 2022 to 13,000 in 2023.
Taylor’s Version has a one-time $3 in-app purchase to unlock some functionality. In 2022 this made $173. This year, it jumped to $1,300, a lovely increase year over year and strongly aided by Speak Now and 1989 re-recordings being released this year.
Personal Best jumped from $1,500 in 2022 to $18,200 in 2023, which continues to blow my mind. The graph below really illustrates just how much things exploded this year.
I use TelemetryDeck to get privacy-first analytics in my apps.
As you’d expect from the downloads and sales, Personal Best’s had a healthly increase in monthly active users, jumping from 2,000 in 2022 to 28,000 in 2023.
(I don’t track active users for Taylor’s Version as it’s not the sort of app people come back to daily.)
I’m reluctant to set another revenue goal for Personal Best. Doubling my revenue from where it is now will be a huge ask, and it feels silly to set a low goal that I’ll easily achieve, so I’m going to avoid a revenue-based goal entirely.
In light of that, here’s my goals for 2024.
I currently have two apps that are nearly ready to be released. One is another health tracker and the other is for tracking your salary. If they continue to just sit on my computer they can’t be used by anyone, so I’m determined to get them out into the world so other people can benefit from them.
This has been on my to do list for years and I never get to it. I really need to do it eventually, and I’m hoping it’ll be the start of morphing Personal Best into the full fitness tracker that I want it to become.
I probably can’t stretch to buying one, but I’m desperate to have a go with one and see what it’s actually like!
I decided to commemorate the five months I spent working on my apps full time by getting my app icons permanently on my arm. If all goes well, they’ll be joined by some more icons soon…
]]>Before I jump in, I appreciate that being able to take a few months off work to focus on a side project then leisurely find a new role is a very privileged position to be in. We shouldn’t take this for granted in our profession.
Interviews are a good thing. They’re a way for the company to find out if you’re suitable for a role, and they’re equally valuable for you to scope out the company and figure out if it’s a good fit for you. However, I think that our industry has gone a bit too far with them and needs to pull back a little.
It feels like in recent years the number of interviews companies insist on has slowly crept up, so now you’re likely to have about five with a company before getting to offer stage. My record this time around for number of interviews with one company was nine. Sure, they weren’t all explicitly labelled as interviews, many of them were ‘chats’ or ‘catch ups’, but I would argue that they’re still a form of being interviewed.
When you’re talking to one or two companies the interview load can be manageable, but when it’s more than that it quickly turns into a full-time job. In my case I have the time for that, but if you already have a job (or other commitments), it might be a struggle. As an introvert I find interviews exhausting, as I have to be ‘on’ for the entire time, and I wish I hadn’t done so many this time around.
My advice to candidates: Filter out unsuitable companies early to avoid doing too many interviews. If a company wants you in the office 4 days a week and that’s not what you want, don’t keep them around as a backup option, just end things. I made this mistake repeatedly and wish I hadn’t. You’re better off focusing on 2-4 companies that are a good fit and spending your energy on those.
My advice to companies: Just as technical debt builds up in your codebases, organisational debt builds up. Consider how to ‘refactor’ your interviews to get the same amount of signal with less time investment.
A lot of companies asked me to complete a ‘take-home’ technical test before proceeding with interviews. I did it for the first few companies that asked, but then I started pushing back when I realised they’re not a good use of my time.
I will always make time for a pairing-style interview where we work on a problem together to see how I communicate and reason through a problem, but too many take-home tests are just a hoop to jump through. Most of the ones I was sent were testing very basic React skills. It felt like I was being asked to spend hours proving that I had the skills that my CV said I had.
At a more junior level there’s some logic to this – maybe somebody fresh out of uni doesn’t yet have the professional skills you’re looking for, but when it’s somebody with over 10 years of experience it feels silly. I’d have to be an incredible con artist to have worked as a React developer for this long without having basic React skills.
Eventually I got to the point where I stopped doing take-home tests, with one exception when the company proactively offered to compensate me for my time.
I’ve been very involved in hiring everywhere I’ve worked, and we phased out take-home tests a few years ago when we realised that they weren’t giving us much useful signal, weren’t respectful of the candidate’s time, and weren’t inclusive to people with commitments outside of work (e.g. parents or carers).
My advice to candidates: Don’t spend time on technical tests that aren’t worth the time investment. Ask if they compensate for it. The worst they can say is no.
My advice to companies: Consider what signal you’re trying to get from a take-home test and see if there’s another way to get that signal. If you insist on a take-home test, pay people to do it.
Note: This section is very specific to the UK.
Quite a few companies I spoke to this time around were offering pension contributions based on qualifying earnings. I was stung by this at my last job so I knew to ask about it this time. If you’re unfamiliar, a qualifying earnings pension means that the company will only make contributions on a portion of your salary, not the whole thing. This portion is set by the government and is £44,030 as of the 2023-2024 tax year. It’s the bare minimum allowed under UK law.
For example, let’s say your salary is £80,000 and the company offers a pension where they contribute 4%. Under a normal pension, they would be contributing £3,200 (0.04 × £80000). Under a qualifying earnings pension, the employer contributes £1,761.20 (0.04 × £44,030). Therefore, you’re not getting a 4% pension contribution, you’re getting 2.2% ([1761.2 / 80000] × 100). The higher your salary gets, the smaller this percentage will become.
In my view, this is a misuse of qualifying earnings. Companies offering the bare minimum legal pension accompanied by a high salary is clearly not what qualifying earnings was intended for. According to gov.uk, the upper limit is targeted at low to moderate earners:
‘It aims to distinguish the automatic enrolment target group of low to moderate earners and the statutory minimum contributions from earners in a higher tax band. These higher earners might reasonably be expected to have access to a pension scheme that offers more than the minimum and are more likely to make personal arrangements for additional saving.’
With one company I got all the way to offer stage before finding out they offered a qualifying earnings pension. It wasn’t mentioned at all in the written offer I received and I only found out because I asked them. Had I not asked, I wouldn’t have known until I was enrolled into the pension scheme after three months of working there.
My advice to candidates: If pension contributions are important to you, find out about the pension scheme early in the process to avoid wasting both your and the employer’s time if it’s not suitable for you.
My advice to companies: If you’re offering a qualifying earnings pension, be up front and own it. It’s better to weed out a candidate early for whom this is a dealbreaker than get to a latter stage and face a rejection.
I’m sure that there are some spectacular recruiters at agencies. If you’re a recruiter who’s taken the time to read this entire post you’re probably one of the good ones. Unfortunately, most aren’t.
This time around I worked with a few agency recruiters and I didn’t find any of them worth the hassle, and I would be wary about doing so again in the future. I was routinely ghosted for weeks at a time, given the wrong time for interviews so I was either really early or really late, and bombarded with phone calls that could have been a one-line email.
A particularly frustrating thing that kept happening was recruiters messaging me on Linkedin to ask if I’m interested in a specific role. I say yes and ask to be put forward for it, then they ask for a phone call. The phone call then turns into them asking about my profile and what I’m looking for, and next thing I knew I’ve stumbled into having an ongoing relationship with them where they keep calling me about more roles, when all I wanted was to be put forward for the original role.
My advice to candidates: As much as possible, stick to dealing with internal recruiters who work for companies directly. In my experience, working with them is brilliant with none of the downsides of agency recruiters.
I learned a lot from my recent experiences that I’ll be taking into my next role to try and improve things for future candidates. If you’re an employer, consider ‘dogfooding’ your hiring process to see how it is for a candidate these days. If you’re a candidate, be mindful about how you spend your time.
Also, consider downloading Personal Best so that my next break between roles can be even longer 😉
]]>The new formatter is very easy to use. Here’s an example of formatting a date using both the old approach and the new one:
let now = Date.now
// Old approach.
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
// Produces 13/10/2023, 10:09AM
let formattedOldStyle = formatter.string(from: now)
// New approach. Produces 13/10/2023, 10:09AM
let formattedNewStyle = now.formatted(.dateTime)
At first this seems like a minor convenience, but when you want to do something more complex the new formatter really shines. Here’s an example of getting the full month and a two digit year, for example ‘October 23’:
let now = Date.now
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yy"
let formattedOldStyle = formatter.string(from: now)
let formattedNewStyle = now.formatted(.dateTime.month(.wide).year(.twoDigits))
As you can see, new format makes for much more readable code, whereas the old format needs you to remember (or use a cheat sheet to get) the specific string you need to get the format you want. If you make a typo, you’ll get an empty string back with no hint about what you did wrong.
For simpler uses the built-in formatter works great. However in Personal Best I like to display dates in a more customised way, like so:
if the workout was today, display the time, e.g. "9:42 AM"
if it was in the last 7 days, display just the weekday, e.g. "Wednesday"
if it was earlier this year, display the day and month, e.g. "11th December"
otherwise, display the day, month and year, e.g. "11th April 2022"
For something like this we need to make our own formatter. As the built-in formatters include lots of useful things like localisation, we should make our custom formatter piggyback off it.
First, let’s make a struct that conforms to the FormatStyle
protocol. To conform to the protocol we need to add FormatInput
and FormatOutput
type aliases, along with a format
function.
struct RelativeDateStyle: FormatStyle {
// The formatter will take dates as inputs, and
// output strings.
typealias FormatInput = Date
typealias FormatOutput = String
// Format the date.
func format(_ value: Date) -> String {
let formatter = Self.customFormatStyle(for: value)
return formatter.format(value)
}
// Return an instance of Date.FormatStyle that's different depending
// on the date passed in.
private static func customFormatStyle(for date: Date) -> Date.FormatStyle {
if date.isSameDateAs(date: .now) {
return Date.FormatStyle(date: .omitted, time: .shortened)
}
if date.isInTheLast(numberOfDays: 7) {
return Date.FormatStyle().weekday()
}
if date.isSameYearAs(date: .now) {
return Date.FormatStyle().day().month()
}
return Date.FormatStyle(date: .abbreviated, time: .omitted)
}
}
// Some convenience functions for checking when dates occur.
extension Date {
private func isSameAs(date dateToCompareTo: Date, componentsToCompare: Set<Calendar.Component>) -> Bool {
let dateComponentsForSelf = Calendar.current.dateComponents(componentsToCompare, from: self)
let dateComponentsForDateToCompareTo = Calendar.current.dateComponents(componentsToCompare, from: dateToCompareTo)
return dateComponentsForSelf == dateComponentsForDateToCompareTo
}
func isSameDateAs(date dateToCompareTo: Date) -> Bool {
return isSameAs(date: dateToCompareTo, componentsToCompare: [.day, .month, .year])
}
func isSameYearAs(date dateToCompareTo: Date) -> Bool {
return isSameAs(date: dateToCompareTo, componentsToCompare: [.year])
}
func isInTheLast(numberOfDays days: Int) -> Bool {
let subtractedDate = Date() - (TimeInterval.oneDay * Double(days))
return self > subtractedDate
}
}
Finally, we can add an extension to FormatStyle
so that we can simply write .relative
when formatting dates:
extension FormatStyle where Self == RelativeDateStyle {
static var relative: RelativeDateStyle {
return RelativeDateStyle()
}
}
Using the new formatter is simple and works just like the built-in ones:
let now = Date.now
let formatted = now.formatted(.relative)
The formatter can easily be extended to include further formatting options like verbosity, but this is left as an exercise for the reader.
]]>Now in September 2023, Personal Best’s recurring revenue just hit 37x of where it was at the end of 2022. In other words, I didn’t just double my revenue, I doubled it five times and then some 🤯.
Here’s a chart of monthly recurring revenue in 2023 (so far).
It’s difficult to put into words how great this makes me feel. I’ve put thousands of hours into Personal Best, and there have certainly been times when it felt like it wasn’t worth the effort I was putting in. I no longer think that.
There are a few things that happened to help me hit this milestone.
In June the startup I worked at was going through layoffs and I volunteered to be made redundant. Rather than look for a new job right away, I decided to effectively treat Personal Best as a full-time job and work on that. I had (and still do have) loads of ideas for how to improve Personal Best, but I continually struggled to find time to do it. With this change in circumstances, I suddenly had all the time I could wish for.
I’m fortunate to have a very understanding partner who was supportive of my choice, if a little surprised at first. On the other hand, my mum was furious at me 🙈
Since going full time on Personal Best, I’ve managed to ship* a lot of stuff:
*Some of it will ship with iOS 17 and watchOS 10
I’m not done, and more features are coming. Watch this space. Currently I’m rewriting the stats screen entirely to be more intuitive and feature lots more useful data about your workouts.
Personal Best is monetised with a ‘Personal Best Pro’ tier inside the app that unlocks lots more features over the free version.
I’ve always struggled with how much to charge for Personal Best Pro, and it’s lead to a lot of introspection about how much my work is worth. From what I’ve heard from other indie developers this is very common. In particular, in the lead-in to Callsheet being released my favourite podcast ATP had some great discussions in this area. I found them really helpful and I think every indie developer can benefit from their expertise.
After looking at apps from my peers I realised that I was charging too little for Personal Best, so I increased the price. It caused no decrease in uptake, so I’m comfortable saying it was the right move. I intend to continue experimenting with the price in the future.
It’s worth noting that I made use of the App Store’s ‘preserve current price for existing subscribers’ option when increasing prices. I didn’t want to annoy my existing subscribers with a price increase, so anybody who subscribed before the increase will continue to pay the original price.
This is the thing that made the biggest difference by far. One day I checked my active trials on RevenueCat to find it was about ten times higher than usual, and growing all the time. I had no idea why this was happening, until the following day where I found out that Personal Best was included in a list on the App Store in the US.
I can’t overstate how much impact this had on Personal Best’s downloads. Overnight I went from having around 1,000 daily active users to 20 times that. I’m very grateful to the App Store editors for seeing my hard work and giving it some exposure, and I hope it’s the first of many features.
I’m setting myself another goal. I’d like to get Personal Best to $1,000 monthly recurring revenue by the end of 2023. I’m currently 73% of the way there and it would be an amazing way to cap off the year.
As I’ve done in previous years, I’ll write a proper end of year summary for how indie life is going, so check back in December to find out how I did.
]]>In 2020 I launched Personal Best, and I realised how much I enjoy making something for myself: being completely in charge of the roadmap, design, engineering, everything. At times it can be difficult, but it’s also really rewarding. As Personal Best has grown I’ve dreamed of being able to work on it full time, but it just doesn’t make enough money right now for me to be able to do that.
Last summer I added a subscription option to Personal Best, and since then it’s been growing really well. Here’s a chart of active subscriptions from RevenueCat.
While the numbers are low in absolute terms, I think this is a promising indicator that Personal Best has the potential to take off and grow a lot more. The problem I’ve always had is finding the time to work on it in addition to having a full-time job.
In the last two weeks I’ve learned that my employer is making some cuts. They’ve offered voluntary redundancy as an option, so I’ve put myself forward to be laid off. While I’m slightly terrified at the prospect of being jobless, this feels like the perfect opportunity to invest some serious effort into growing Personal Best.
I plan to work full time on it for the next few months. I have so much I want to work on, and I’m very excited to have the chance to do that. The redundancy package I’m getting is fairly modest, but if I’m frugal I think I can stretch it out for a few months until I need to find a new full-time role.
The part of indie life I’m worst at is marketing. I’ve pitched my apps to so many journalists and YouTubers but haven’t got very far. It’s something I need to improve, and a significant chunk of my time off will be dedicated to this.
If you have an audience that’ll appreciate best-in-class workout insights, consider sharing Personal Best with them. It’s got a high App Store rating, thousands of active users, and supports the latest iOS features. Promo codes are available on request (drop me an email, tweet or toot), and I’m available for interviews.
]]>During this journey I’ve come across lots of helpful blogs, podcasts and books, and I thought I’d document them here in case they’re useful for others.
Under the Radar is a fortnightly podcast about independent iOS development, hosted by Marco Arment and David Smith. Marco is the creator of podcast app Overcast, and David has a bunch of apps, but is probably best known for WidgetSmith.
It’s been running since 2015 so there’s a long back catalog of episodes to delve through, and I really like that they cover lots of different aspects of indie life, like support, marketing, and how to prioritise when you’re a solo developer.
Every episode (except one) is never longer than 30 minutes, so it’s a really easy listen for when you don’t have a lot of time.
If you enjoyed Under the Radar, you’ll probably also enjoy David’s blog. In late 2022 he started a series called Design Notes Diary, where he talks candidly about his thought process when designing and building his apps.
It’s really interesting to see David’s thought process, and I particularly like the posts where he goes deep on accessibility.
Keep Going is a book by Bardi Golriz about his journey as an indie app developer. As well as featuring real revenue numbers, it goes into lots of detail about his thought process behind developing new features for his apps Appy Weather and ruff, along with some surprises along the way. I really enjoyed reading this and I cannot recommend it enough.
🔗 blog.curtisherbert.com/tag/slopes-diaries/
Slopes Diaries is a section of Curtis Herbert’s blog. Curtis is the developer behind Slopes, an app for tracking snow sports. In Slopes Diaries, he talks candidly about the things he’s doing to grow Slopes and make it better.
One thing I love about it is that like Under The Radar, it goes all the way back to 2015. You can read old issues with the benefit of hindsight and see how much Slopes has grown. Curtis also shares revenue numbers, which gives me optimism for what I can achieve myself as a solo developer.
Becky is the creator of several apps, most prominently YarnBuddy, an app for knitting enthusiasts. Her blog covers a lot of different aspects of indie life and aspirations. Her WWDC 2022 reflections post in particular was something I found really great this past year.
AppForce1 is a podcast from Jeroen Leenarts, sharing news and tools relevant for iOS developers, as well as the occasional interview. I like how it covers a broad range of topics, and each episode is very conversational and easy to digest.
If you enjoy the podcast, Jeroen also has a blog which is similarly filled with great content. I like that is
Indie Dev Monday is an interview series, where each week Josh Holtz interviews a different indie developer. I look forward to receiving this each week and reading stories from other indie developers about how their apps came to be.
Full disclosure – Josh kindly featured me in this newsletter last Summer.
Do you have any good resources for indie iOS developers? Let me know on Mastodon @shaundon@mstdn.social!
]]>In keeping with the tradition, here’s my 2022 in review.
At the end of 2021, I set myself five goals for 2022. Here’s how I did on each of them.
Status: Missed ❌
I tried, but unfortunately I didn’t make the cut. At WWDC Apple advised me to fill in the form at appstore.com/promote every time I ship something notable. I did do this a few times, but I think I could be doing more.
Status: Achieved ✅
Smashed it. Because of my Lock Screen widgets, I earned some coverage around the time of iOS 16’s launch in TechCrunch, 9to5mac, CNET, as well as some other websites.
All of the above are listicles featuring apps that have Lock Screen widgets. I’d stil love to eventually get a full article just about a new Personal Best feature, but that’s a task for another day.
Status: In progress ⏳
For most of 2022 I didn’t make meaningful progress on this. My day job as an engineering manager at a startup had me feeling like I didn’t have enough energy to work on my apps outside of work.
However, recently I’ve made some changes to my work situation which are beginning to help.
Firstly, I’ve transitioned out of management and I’m now back to being a software engineer. I realised that while there were a lot of aspects of management I really enjoyed – like helping to drive best practice and helping others to grow – I’m happier focusing on technical problems rather than people ones.
Secondly, I’ve switched to working ‘compressed hours’, which means I still work 40 hours a week, but over four days instead of five. Instead of working eight hours each day, I now work ten hours. This means that on Tuesday to Friday I have long days, but it gives me a whole extra day off each week to run errands and work on my apps. It’s only been two weeks but it’s already made a huge improvement to my mental health and productivity.
In both cases my employer was very understanding about my situation and needs and I’m really happy that we were able to find a solution that worked for us both.
Status: In progress ⏳
There were a few places that Personal Best felt a bit janky, with some dropped frames or bugs. While no app will ever be bug free, I think I’ve made some good progress on this. Of course, I still have a huge list of improvements I want to make, but realistically I think that’ll always be the case.
Status: Achieved ✅
I’m very happy with the amount of things I shipped this year. I’ll go into more detail about them below, but suffice to say I think I managed to deliver a lot of new things.
This year I shipped a lot of new stuff.
In January I was struck with inspiration with a new app idea, so I put Personal Best to one side for a few weeks and worked on that.
I’m a huge Taylor Swift fan, and recently she’s been re-recording her first six albums in order to own her masters. I wanted a way to quickly replace the old versions of her songs with the re-recorded ones in my Spotify playlists, and rather than spend fifteen minutes doing that like a normal person, I decided to spend five weeks’ worth of evenings and weekends making an app to do it. On February 13th I launched Taylor’s Version.
I made sure to include plenty of polish, so as well as replacing songs I added things like a quiz to do while you wait for your playlists to load, lots of custom app icons (one for each album), and a few extra features like a button to save a new playlist of Taylor Swift songs.
I knew that this was quite a niche idea for an app so I wasn’t expecting it to take the world by storm, but to be honest I had hoped it would garner some press coverage and maybe even a little virality. While it’s niche, it felt perfectly placed for a few “Look at this silly app somebody made” articles. I think this does tie in to me continuing to suck at marketing, which is something I still need to improve at. A lot. I emailed and tweeted a lot of journalists, posted it on various subreddits, sent it to Taylor Swift fan sites, but unfortunately it never went anywhere.
Even with the app not doing that well, I don’t regret the time I put into it. I’m proud of the level of polish I was able to achieve, and I gained experience with a few things I haven’t yet done for Personal Best, like iPad support, working with external APIs, and sending remote push notifications using Firebase.
Having said that, as I have limited time I’ve decided not to add any new features to Taylor’s Version, because realistically I know it’s unlikely to get more downloads than it already has. I still 100% support it, but it’s now in a state that I consider complete. I did want to add support for Apple Music, but limitations in Apple Music’s API mean that third-party apps can’t edit playlists, only make new ones. I could work around this, and I may revisit it in the future, but for now I’ve decided not to pursue it.
TL;DR Downloads are down but sales are stable and active users are up.
Personal Best’s downloads decreased this year by quite a lot.
In 2022 Personal Best was downloaded 2,160 times, compared to 14,200* in 2021 and 7,000 in 2020.
*10,570 downloads came from listing my app in Apps Gone Free, which gave me a lot of downloads but they didn’t lead to any increase in users or sales. Excluding these gives me 3,630 downloads.
Taylor’s Version was downloaded 2,490 times in 2022. Even though Taylor’s Version got more downloads than Personal Best, the sales tell a different story.
Taylor’s Version has a one-time $3 in-app purchase to unlock some functionality. It sold 57 units this year, leading to $173 in sales.
Personal Best sold 114 units of lifetime purchases ($5, raised partway through the year to $15), leading to $1,100 in sales. The new monthly and annual subscriptions made $234, and I also made $16 from the tip jar. That puts my annual totals at around $1,500. Once Apple takes their cut, along with the various digital service taxes from government that they pass on to app developers, I’m looking at about $1,200.
In previous years I had no recurring revenue, but thanks to my new subscriptions I currently have roughly $250 annual recurring revenue.
My revenue in 2021 was also around $1,500, so while growth would have been even better, I’m pleased at being able to maintain my current revenue. Of course, I’d like to grow this in 2023.
Thanks to TelemetryDeck I have privacy-first analytics in my apps.
In December 2021 Personal Best had around 1,300 monthly active users. In December 2022, it’s now around 2,000 monthly active users, a nice increase year on year.
Taylor’s Version isn’t the sort of app people come back to day after day, it’s more of a ‘one and done’ app, so I don’t look at my active user numbers there.
I have a few goals for 2023.
I’ve deliberately taken on fewer goals than last year, because I want to focus primarily on increasing my revenue. At the end of the day, if I want to turn my apps into a sustainable income stream then the only thing that matters is revenue. Doubling it will take me to about $500 annually, which is still very modest, but would be a great amount of growth.
Here’s to 2023.
]]>