Explainer Projects Technical

Using Swift string literals to load bundle resources

My new iOS fiction project relies heavily on text.

That means I want to make it easy to create that content anywhere, and I want it to be frictionless to drop it into the project as needed.

My solution: Markdown files I can load from the bundle using string literals. Look how easy:

let markdown: MarkdownFile = ""

Here’s how to do it.

String literals

In Swift, you use string literals all the time. Usually to initialize strings.

let string: String = "Hello, I am a string."

But Swift includes a protocol called ExpressibleByStringLiteral. Which means if your Swift type adopts it, that type can be initialized with nothing more than a string. While this is immediately convenient, it has real power for assets that need tedious boilerplate. Say, anything that needs to be loaded from a bundle.

Basic example

struct MarkdownFile: ExpressibleByStringLiteral {
    let bundleName: String
    let rawMarkdown: String?
    init(stringLiteral: String) {
        bundleName = stringLiteral
        var loadedMarkdown: String? = nil
        if let filepath = Bundle.main.path(forResource: bundleName, ofType: nil) {
        //By skipping the ofType argument above, we'll match to the first file whose name
        //exactly matches bundleName
            do {
                let loadedString = try String(contentsOfFile: filepath)
                loadedMarkdown = loadedString
            } catch {
                print("Could not load string: \(error)")
        } else {
            print("Could not find file: \(bundleName)")
        rawMarkdown = loadedMarkdown

Here’s a basic example of a MarkdownFile struct. It knows two things about itself: the name of the file used to initialize it, and any string it was able to load from a file in the bundle with that name.

On init it goes looking for a bundle resource matching the name it was provided through the string literal. If it finds one, and it can load its contents as a string, those contents are stored to rawMarkdown. If not, rawMarkdown returns nil.

This is already pretty convenient. Again, to initialize, all you need is:

let markdown: MarkdownFile = ""

But we can take it further.

Adding convenience

The MarkdownFile struct can be responsible for converting its contents into a display representation, as well. Let’s add a computed property to parse the Markdown into HTML. I’ll be using Ink for this, but you could use any project—or convert it into something else, like NSAttributedString.

var htmlRepresentation: String? {
    if let raw = rawMarkdown {
        return MarkdownParser().html(from: raw)
    } else {
        return nil

Putting it all together

With our output property all set up, we have a small, convenient API for handling Markdown files in any way we want. Here’s how we use it:

let markdown: MarkdownFile = ""

if let html = markdown.htmlRepresentation {
    webview.loadHTMLString(html, baseURL: nil)

self.title = markdown.bundleName

Behind the scenes, lots of stuff is happening to load and parse the file. But when you need Markdown across your project, you need only concern yourself with a filename. If you want to change any part of how this works later on, you have a single struct that’s responsible for all the Markdown behavior in your code.

Full example code here.


Post Mortem: Digamo Beta

I’m excited to share a new project with you. It’s called Digamo and it’s a macOS app built for people who have, or should have, 1:1 meetings at work.

Digamo is a collaboration with my dear friend, podcast co-host, and occasional boss, Nicole Sanchez. It’s something we’ve been thinking about doing for years. Back in April, we decided to go for it.

What the research said

Before diving headlong into development on a project we were both excited to build, though, I asked Nicole to conduct some user research. We had some strong convictions already:

  • Managers didn’t often get the support or training they needed to do their best work
  • Healthy, regular communication, especially between bosses and their reports, was essential to inclusion
  • We could improve both of these situations by creating structure and guidance for common workplace communication through software

We still believe these things. But if we were going to invest the energy and effort needed to produce a whole software product, I wanted more information from the sort of people we wanted to serve.

Nicole gamely conducted interviews with people managers, digging into their processes for communication and how they managed the many meetings and conversations that were essential to their daily work. One theme emerged clearly:

Many managers had an enormous Google Doc they were using to keep track of… everything. The text was monstrous and unstructured. Things often got lost over the longer term.

This was an opportunity we hadn’t considered, but it fit into our goals well: software could also structure the information that emerged from these conversations. This would make it possible to slice, filter, search and summarize that information at a later date. In addition to training managers on good communication, and encouraging that communication, we could also provide value by capturing and summarizing outcomes.

This insight was a North Star that guided our product development, and we share it eagerly with you. People are drowning in information. Help them organize it better, you’ll make a better workplace.

Starting development

At first, we weren’t sure how eager people would be for yet another piece of software at work. We started with the goal of reaching an alpha that would run on an iPhone, sync with your work calendar, and help you prepare for meetings.

The idea here was that we could meet our users in their spare time, relieving their anxiety about upcoming meetings by providing a clear structure for the sorts of things you do before and after sitting down. You might have some decisions that you want to make sure are made by those in attendance. We suggested warmup activities to build rapport between attendees. How will you know the meeting was successful?

We prompted the user to think about, and write down, responses for all of these and more. A neat party trick was that the output was nicely formatted into an agenda you could email.

There was just one problem: the iPhone was a bad fit for these tasks. No one wanted to look at their phone in the middle of a meeting, and prepping for meetings just isn’t the sort of casual task anyone is eager to do with their thumbs.

And we were about to have company.

~Validating the market~

When competitors join the scene, it’s Silicon Valley tradition to smile fixedly and announce how excited you are to see them validate the market. There’s some truth to this posturing, though: it’s encouraging to see others converge on a conclusion you’ve reached on your own. Maybe the timing is right for your idea.

We took the validation to mean we could dispense with the idea of building this as an iPhone toy. We would proceed as though the world was ready for another piece of workplace software.

Still, no amount of business aphorisms would change that we were now approaching a problem alongside teams that were bigger and better-capitalized than we were. Moving to a desktop format would help our project be taken seriously, but we were still outgunned.

For our vision to have a future, we would need to chart an unconventional course. As the sole engineer on the project, it was my job to make things work while keeping things lean. If people were going to use this on the job, it had to be sturdy and reliable. And we had to ship quickly, with competition around the corner. These are interesting constraints to manage when your developer bus factor is one.

What are the decisions only we would make?

As outsiders, Nicole and I had little hope of raising money for this project without showing significant progress, so that ruled out bringing on more engineers. Without a larger crew, I didn’t feel confident in using web technology for something so mission critical. I’d be tied to my keyboard indefinitely, protecting the infrastructure that kept the product alive.

So we tried something different: decentralizing our software entirely. Our approach turned to a native macOS application that would host and maintain all its own data.

Gif: Man points knowing at his head and grins.
Server can’t go down if you don’t have a server.

Because we weren’t starting with non-negotiable hyper growth as a requirement, this old-fashioned approach would be viable for us. The more we explored it, the better it fit our values. By designing our project without a centralized server infrastructure, we could guarantee that no one else surveilled your usage of it. This was important because we wanted our users to be real about the thorny communication issues they encountered at work. We wanted them to trust our product the way they would their own physical notebook.

I’ve seen complex, misconfigured workplace software that leaks sensitive details like a sieve. It’s a privacy nightmare. Our approach sidestepped all of that.

We also got some practical benefits: the application could run locally without needing to package an entire web browser under the hood, so the binary size is just 20 MB. The native code is also efficient. Even with a large data set, it doesn’t seem to need more than 50-100 MB of system RAM. So, as a bonus, we got a minimal resource footprint and the ability to run without an internet connection thanks to building a native desktop app.

For object graph maintenance and persistence, the app uses Apple’s Core Data framework. Ten years after my first Core Data projects, I’m impressed with how much easier it is to work with these days. The framework still has a meaningful learning curve and I’d be bullshitting you if I said I grasped all its complexities. Nonetheless, I found it a straightforward way to maintain all of Digamo’s relationships and generate the user-specified queries needed for summarizing data.

As a long time iOS developer, I have to take a moment to gush about Cocoa Bindings. They make UI development so fast and easy. With the advent of SwiftUI, they may be destined for sunset. I’m glad I finally got a chance to use them, after years of envy from afar.

We still ended up with a lightweight server to provide automatic updates, delivered by the excellent Sparkle Project. This is just good practice if you’re distributing outside the App Store, but it’s also essential for a beta. Our users will always have access to the latest features if they opt-in to these updates. Still, the server could go down for a weekend and no one’s work would be interrupted.

Reaching the home stretch

Meanwhile, Nicole’s work in the field was yielding some interesting feedback.

In her capacity as an expert in organizational design and workplace culture, Nicole spends a lot of time training leaders in the basics of good management. She had this slide:

Spreadsheet depicting a grid of multiple categories of data to collect during 1:1 meetings.

People were grasping at it more than anything else in her trainings. Just a simple spreadsheet, but it offered so much clarity about how to consistently do 1:1 meetings. Weeks after their first introduction, people were still using spreadsheet as part of their ongoing work.

We had our product.

Screenshot showing Digamo's note input interface

From there, we dumped the complexity of calendar syncing and meeting prep. Our laser focus was one thing: supporting 1:1 meetings. Setting expectations for the topics people should cover, helping them keep track of how often they discuss things like feedback, professional goals, time off, major wins, and more—not to mention helping our users dig up those discussion notes later on.

Screenshot of Digamo's sentiment capture interface

Nicole’s spreadsheet also asked people to keep track of sentiment, and we worked together to find a structure for this fuzzy data so that we could surface it in the UI.

Screenshot of Digamo's emoji timeline

Each teammate gets their own emoji timeline, showing the progression of their sentiments over time. We can also give you a bird’s-eye view of sentiment across your team at any given moment, as the teammate selector list view displays a summary of the most recent entry. That view also flags any teammates who have been neglected in your 1:1 meetings, always showing people with meetings furthest in the past at the top of the list.

Screenshot of Digamo's teammate list

Emojis are subjective, so it’s possible to set your own preferences for them, too.

Screenshot of Digamo's emoji preference panel

With all this data structured, it’s easy to retrieve later on. Just choose a teammate, a time frame, and the notes you care about, and Digamo will generate a summary. We can also do the same with your whole team. We know how much work performance review season can be. We’re excited to see how this feature helps streamline those processes, reminding you of the full picture of your team’s challenges and accomplishments.

Screenshot of Digamo's summary generator.

So that’s Digamo. It’s your private, digital notebook for recording 1:1 meetings. Thanks to the Vaya Consulting team, and especially our friend Dr. Nick Bedo, for the support in getting this out the door.

Hidden in the product are a handful of deliberate bias mitigation strategies:

  • Summaries help mitigate recency bias, surfacing details you might have forgotten in the day-to-day maelstrom of surviving work.
  • The teammate list really wants you to see anyone whose 1:1’s you’ve been dodging.
  • By collecting your sentiments, in addition to those of your 1:1 counterparts, Digamo can help you compare how you’re engaging with one teammate versus another.

What’s next?

There’s so much more we want to do with Digamo, but it’s time to get it out into the world. High on the list are things like automated backups, sync with other devices, and a return of calendar sync. We’ll get there. Meanwhile, we’ve got the start of something a lot of folks have been asking us for. We hope the structure and conveniences provided by this initial beta will be helpful in your daily work.

While a lot of the people we know use a Mac, we know a macOS app doesn’t cover the full spectrum of people who need this product. We’ll be thinking about how to deliver this more broadly in the future.

Meanwhile, Digamo is free to download and use while we develop it further. Download it here.

Are you an investor who wants to help us deliver workplace technology like this for more people and more use cases? Get in touch.


Saving a Non-Profit Six Figures a Year Using Squarespace, Airtable and

I swallowed hard.

I was staring at a four-gigabyte website dump. There was no documentation. There was no version control. Just thousands of PHP files across dozens of directories, plus a sprawling MySQL database.

What had I gotten myself into?

The files I was looking at comprised the website for EveryoneOn, a non-profit that connects low-income families with affordable internet access. We’d been working together for a few years, since ConnectHome in the Obama era when they approached me looking for leads on new web technology providers. Their existing contract web developer cost as much as an entire senior salary, and they wanted more affordable options.

I offered them a sweetheart deal with one goal: get them to a point of self-sufficiency. There are perverse incentives for a contract IT provider — it makes sense to increase complexity, making the client more dependent, ensuring future billings. I wanted to break that cycle.

But as I looked at the innards of their site, I wondered if I was in over my head.

Technical complexity

There was a reason EveryoneOn was paying thousands of dollars each month just to keep their website online.

This thing was complicated. Multiple IT providers were involved in its maintenance over the years. A monolithic database drove everything from WordPress content to site analytics to the API that let users search for affordable internet offers.

This wasn’t my first LAMP server, but getting everything up and working took a couple of weeks of troubleshooting. With no documentation, it was a matter of trial and error to figure out how to configure Apache and MySQL to work with the years of PHP layered all over the place.

This complexity had everyday costs for EveryoneOn’s staff, too. Many changes to the site and its content had to happen through their contractor. This made the process slow and limited the control they had over their web presence.

It took some work, but once the replacement server was in good shape, we switched the domain over. My next tasks: putting out the occasional fire and coming up with a better, simpler, cheaper technology strategy.

Other people’s servers

A tangle of interdependent services hosted on a single server meant a big component of EveryoneOn’s IT costs was having someone on-call to fix things when they broke. But if we could use technology hosted and maintained by third parties, we could offload that burden to them.

I argued that the more custom code EveryoneOn was responsible for, the more expensive their technology had to be. Yet, the tasks EveryoneOn needed their technology to accomplish were common:

  • Content management system for a website
  • Analytics and statistics about web usage and offer adoption
  • Structured data about internet offers

Having bespoke versions of all of these didn’t provide a lot of value, relative to the steep costs they imposed.

But if we could use off-the-shelf tools, it would be someone else’s job to keep them online, updated and secure. Instead of spending thousands of dollars a month, the organization’s fixed technology costs could be a few hundred dollars per year.

Best of all, because these products were built to sell to thousands or millions of users, they would be more accessible and intuitive than what we started with.

Here’s how we replaced a tangled soup of PHP and MySQL data with accessible, cost-effective third-party services:


Cost: $218/year

The heavily customized WordPress installation gave way to Squarespace. With 24/7 customer support and so many e-commerce customers using it every day, we knew we could rely on it to stay up and running. More than that, Squarespace has an easy-to-use editing interface for both text and page layout. Now, EveryoneOn could directly edit their website design whenever they wanted.

The other part about Squarespace I liked was its design constraints. A global styles palette keeps everyone’s pages consistent. Multiple people can build pages for the same site and the typography and colors will be the same across all of them. Any changes to those styles automatically propagate later on.

For an organization without an in-house graphic designer, these details mattered a lot. Leaving them with a website that steadily drifted away from professionalism and polish in its design would endanger their credibility and their mission. No amount of automation will protect you from an ugly site, but guardrails baked into the product can go a long way.

Google Analytics

Cost: Free

Meanwhile, a large subset of the custom analytics dashboard could be handled by Google Analytics. The basics of Google Analytics are pretty set-and-forget: make sure a script is included in your page header template. In Squarespace, you just paste your site ID into a settings field.

But Google Analytics can do more than passively track pageviews. You can also track events important to your users’ journey through a site by writing some custom JavaScript code. Alongside custom event tracking, it’s possible to attach additional “dimensions” of data unique to your web app. Between this and Google’s point-and-click custom report builder, you can assemble detailed reporting unique to your business needs. From there, Google does the heavy lifting of slicing a growing data set across whatever time periods and dimensions you’d like.


Price: $12 per user per month (non-profit pricing)

Even with Google Analytics handling much of the site behavior stats, we still needed a database.

But more than that, we needed a tool that made data accessible. MySQL is arcane technology. Even its popular GUI, PHPMyAdmin, isn’t especially user-friendly. If you’re not a software developer, and if no one has bothered to write some code exposing the database content you want, the data may as well not exist.

Airtable changes the game considerably. It has all of the user-friendliness of spreadsheets, with all the technical benefits of a database. It’s easy for anyone to read, filter or edit Airtable content — even niceties like copy and paste work great.

Yet, as a database, fields are formatted, structured and validated. You can create relationships between records in different tables. Because of this, Airtable is even better than spreadsheets for easily setting up fields that lookup data automatically.

It’s by far one of my favorite pieces of modern software.

In Airtable, we had a tool that could store relationships between zip codes and relevant internet connectivity offers. It could store data about partner organizations, and display it on the website when relevant. It could even store real-time events also being sent to Google Analytics, letting EveryoneOn audit the data at the foundation of their reporting.

Because Airtable has an API.


Price: Free

That brings me to Glitch. If you’d told me six months ago I was using Glitch for website infrastructure, I wouldn’t have believed you.

But while we had plenty of great new tools maintained by someone else, there was one snag: a small percentage of EveryoneOn’s web technology would still need custom, server-side code. Not thousands of files’-worth anymore, thankfully.

Still, the process that matched user zip codes to the internet offers available to them, then displayed the offers on a web page, was unique, and Squarespace offers no facility for server-side code. I fretted over this for a few months. My initial plan was a $5/month DigitalOcean instance to host the custom code.

This wasn’t ideal. The barrier to understanding how to administer something like that can be significant. The more complex their custom technology was, the more challenging and expensive it would be for EveryoneOn to make changes to it in the future. For so much of the website, I’d managed to find hosted tools that were accessible to many technology skill levels. I hated the thought of an inscrutable, hard-to-reach black box sneaking into the process after all.

Around this time, Glitch came out of beta. Having a look at its limitations, I realized it could more than handle EveryoneOn’s traffic needs. Glitch provides on-demand node.js sandboxes where you can write any server-side code you want. Even better, there’s built-in, automatic version control.

While I was a novice with node, it didn’t take long to build a replacement implementation of EveryoneOn’s offer API using Airtable as the storage mechanism. From there I built a few more Airtable wrappers for other data the organization needed to display on its website.

I was especially tickled when it came time to migrate their offer search widgets. These were PHP-based micro-pages that allowed third parties to embed EveryoneOn’s search into their own site. Using Express and the EJS templating package, it was quick work to build an emulator that served widgets at the appropriate .php URLs. So the implementation changed completely, from PHP to JavaScript, but the change was transparent to end users. No one had to make any updates to their pages.

What I found in Glitch was the perfect toolbox for adding custom functionality to a website. It’s easy to get started — you just click a button to create a new project. You don’t need to understand ssh or SFTP to make changes later on. You don’t need any special software. If you have a web browser, you can edit the code.

Which means that anyone who knows or can learn JavaScript can expand or enhance these services later on. With automatic version control, the sharp edges are dulled. If they break it, they can roll back to a known-working state no problem. Because of Glitch’s per-project sandbox model, each service is isolated. Breaking one doesn’t harm any others.

My one gripe with Glitch: I’d feel so much better if I could give them some money to ensure their long-term success. Cash for custom domain names, cash for higher service limits — hell, even a tip jar.

Still, Glitch allows you to easily export a project to GitHub. Worst case scenario, someone can dump out these projects and run them on a node server.

Long term effects

These services aren’t a 100% replacement of the beast we started with, but we were able to handle the vast majority of the old tasks using tools that are much easier to understand, and orders of magnitude cheaper. Annual fixed costs are down from six figures to under $1,000. While some custom code remains, it’s only around a dozen files, it’s well-documented, fully version-controlled, and editing is easy. A digital binder of documentation, hosted as a Google Sites wiki, provides an overview of all services, with explicit guidance on how to make changes. Best of all, there’s nothing for a future contractor to hold hostage. EveryoneOn directly controls every account that drives their web presence.

The project dramatically reduced the scope of custom code to maintain, while also bringing the number of servers to administer down to zero.

I don’t want this to be seen as a post bashing WordPress, or LAMP generally. Sometimes that’s the perfect combination of tools for a job.

But while running your own server was once the only way to solve these sorts of problems, we now have more options available. It’s exciting to see how a handful of hosted services can be composed to create a dynamic site that reports ample business intelligence, while still being user-friendly and cost-effective for a non-profit. Instead of needing an experienced system admin, now EveryoneOn can advance their web needs with student interns or volunteers — anyone willing to learn basic JavaScript.

While saving EveryoneOn some cash from their operating budget is a great start, they still need help getting more than 60 million people connected to high-speed internet at home. Kick them a few bucks to support their mission closing the digital divide.