Explainer Technical

All software is a service (or: Adobe was right all along)

Today, Photoshop is a utility that comes out of a digital tap. Pay Adobe a few bucks a month, you can edit your photos as much as you want. In truth, it always worked this way. Yes, the payment implementation has changed and the charges are more granular.

But Adobe anticipated SaaS long before products like Basecamp or Mailchimp hit the scene. Every few years, they’d tack on a few features you didn’t need or ask for, and charge you a few hundred dollars for the latest version. That’s how an app called “Photoshop” has everything from video editing to 3D transforms tucked away in a corner.

While a lot of us rolled our eyes at this and called it “bloatware,” Adobe had a point. Photoshop was used by professionals and professionals are often at the bleeding edge of computing capabilities. Full-featured web export for a variety of image formats, for example, became non-negotiable in the space of just a few years. Adobe had to pay for development of that capability somehow.

Meanwhile, the hardware and operating systems that host applications are continually changing as well. For the Mac, Adobe has had to pay for the transition from Classic Mac OS to Carbon, the transition from PowerPC to x86 processors, the transition to 64 bit architectures, and the annual march of macOS updates as well. There’s enormous variation in the costs involved in these transitions, but what’s constant is that computing devices are moving targets.

Now, instead of epochal versions of Photoshop at several hundred dollars per upgrade, you pay Adobe monthly.

For software that sits on your own hard drive. Run by your own CPU.

But as we can see, keeping that software viable on your fast-evolving computer is a job. A service. While they may not be hosting the software directly, they’re still working behind the scenes to keep it all running.

Even indie developers are coming around to this point of view. Note organizer Agenda, for example, offers a freemium-subscription hybrid. Get the basics for free, then receive any improvements they make to Agenda during the life of your subscription. Once your subscription ends, you keep those improvements, but must subscribe again for any additions.

Sketch, which came out of nowhere to steal the hearts and minds of designers away from Illustrator, ironically mimics the Adobe’s license scheme of yore. Periodically, your license stops being valid for the latest versions of Sketch and you must pay for an upgrade to continue receiving the latest and greatest.

1Password recently joined this train. Cynics may attribute this to their recent venture funding, but even absent that influence, the move was inevitable. For both security and usability, your password manager needs to be right at the edge of the curve with every new OS update.

One interesting exception here is Procreate, the extraordinary, feature-packed painting app for iPad, used with great enthusiasm by professionals and amateurs alike. I’ve paid for Procreate once, just $5, and it continues to get better and better. How is this viable? The answer may lie in the ubiquity of Procreate’s customer base. While not every iPad user is a pro artist, every iPad benefits from the addition of Procreate. Who doesn’t want to doodle from time to time? And at just $5 for way more power than most users will ever need, how can you say no?

It will be interesting to see how long this model can sustain Procreate without the addition of In-App Purchases. They’ve unlocked another wave of customers by porting Procreate to iPhone. Tellingly, it’s not a universal app. The iPad and iPhone versions are separate SKUs. This is entirely reasonable from any customer perspective—reworking the Procreate interface to fit on the iPhone surely took a lot of effort. Still, it’s also a means of extending the value of the core IP, since the underlying graphics rendering, compositing and brush systems can now be monetized for two products instead of one.

So even when you’re not hosting the software, if your customers are subject to regular hardware and OS update cycles, your job is to provide a service. Finding a revenue model that lets you provide that service is essential, otherwise you’ll end up with disappointed customers and a slowly degrading codebase.

I hate to admit it, but Adobe was right all along.


Yes, I’ve had to give this a lot of thought over the last two months. My new app, Tallymander uses a subscription approach for these reasons. With an exception: there’s a premium escape hatch for people who hate subscriptions. For less than the cost of a couple years of service, you can unlock all Tallymander features forever. It’s a deal for early adopters—a kind of venture bootstrapping. Take a chance on a new product by paying up front, enjoy the fruits as ripen.

For all the reasons above, this option won’t be around forever.

Personal Projects Technical

Designing, building and shipping an iPhone app in five weeks with SwiftUI

I was laid off last month.

I’m hardly alone in this. With COVID-19 upending the game boards of every industry under the sun, lots of us have lost jobs.

Of course, this makes finding my next act complicated. Even in the technology business, thousands of highly qualified professionals have been cut loose in recent weeks. It’s just not a great time to look for a job.

So I decided to go back into business for myself.

The first step in that process was building an app as both a source of income and a showcase of my abilities as a product designer and software developer. I’ve spent every day, night and weekend of the last five weeks designing, building and shipping an iPhone app. Part of what made this possible was SwiftUI. Apple’s new declarative UI framework makes building touch interfaces faster than ever. Even with the pitfalls you’d expect from a brand new technology, it is already the best and fastest way to build an app for iOS.

Choosing a product

Once upon a time, I fell in love with product design, code, and shipping software in Second Life. I didn’t think I’d do anything else with those skills—I just had a lot of fun with them, and paid a few bills on the side.

But in 2008, when the App Store debuted, I saw a chance to put those skills to work for a truly global audience. I wanted a career building software.

I knew no one would hire me without experience, so I set about giving myself some. I built a raft of products for the iPhone—a game, a travel organizer, a simple app for shopping at IKEA. I sold a few of each.

But there was one app that truly stuck. Called Tallymander, it was a flexible tool for counting and math. Early on, to my shock and exhilaration, Apple featured it on the front page of the App Store. I heard from customers across all walks of life—research scientists, drywall installers, collectible card game enthusiasts—who were in love with it and had feature suggestions.

Look at that shiny, glossy UI. Were we ever that innocent?

Eventually, I got my career as a professional product designer and iOS developer. In fact, Tallymander opened the door: the CEO at my first startup gig was an early Tallymander customer. But I didn’t have time to maintain my indie projects, so I took them off the store.

No one really minded. Except my Tallymander customers, who emailed, bereft, for months.

Like an estranged friend, I’ve thought of Tallymander constantly in the years since. I loved that product. I loved that it was useful to people. And I loved my tiny contribution to the Great Conversations of products like VisiCalc, Excel, and Airtable. All of these make data and algorithms something tangible, experimental and fun.

Over the years, I’ve wanted desperately to revisit Tallymander. But the level of effort it takes to build an iOS app is significant. Quality is paramount, for one thing. You have to do things well to thrive in that environment.

But UIKit, Apple’s original UI development framework, can be such a slog. It was hard to maintain the velocity needed to get a project off the ground.

Last month, I was given more time than I asked for. And SwiftUI, Apple’s replacement for UIKit, had gelled enough to build with.

This confluence of events meant one thing: it was time to bring Tallymander back.

SwiftUI: the fast, modern way to build an interface for Apple platforms

UIKit emerged for third-party developers in 2008. It was a very different world. There were only two models of iPhone. Both phones had identical screen sizes. The iPad was still a secret project, known only to a few.

In the years since, iOS evolved to accommodate a vast array of devices. You can use UIKit to target all of the different iPhones and iPads. Using tools like auto layout, you can fiddle your way to a view that looks right on any size screen. Eventually.

But in a lot of ways, UIKit and friends feel pretty creaky. Want a table-based UI? You’ll have to implement dozens of lines of data source and delegate boilerplate just to get started. Want to draw shapes? It’s an exercise in such tedium that an entire app exists to spare you the worst of it.

UIKit also let you create bugs if you weren’t careful. Cocoa Bindings never made the journey from macOS to iOS. With no reactive frameworks built in, a typical approach was to write UI values in one pass, maybe when the view appears, and read back any inputs in another, perhaps when it disappeared. This could work, but the space in between was room for bug city. Frameworks like RxSwift emerged to fill the gaps, but didn’t have the widespread, universal adoption of Apple’s many iOS libraries. You could cobble something together yourself with KVO, but managing subscribers was its own headache. Get it wrong, you get crashes.

Apple’s edge in mobile was multiplied dramatically by the existence of developer tools and frameworks adapted from Mac OS X. Preserving that edge meant investing in new developer-focused technologies, especially Swift, which could be fertile ground for a new generation of APIs.

That strategy is has borne some meaningful fruit in technologies like SwiftUI and Combine.

SwiftUI is declarative. You tell it what you want to see, it figures out the details for you. This is a departure from UIKit, where every point of every frame of every view was a detail the developer could worry about.

Tell SwiftUI you want a field here, a slider there, and a toggle switch if the slider exceeds a certain value, and you’ll just get that interface. Maybe it won’t be exactly what you had in mind, so you’ll add a few modifiers. Primp with some padding, polish with some rounded corners.

An entire form with conditional fields specified in under thirty lines. Some of these call out to additional, more complex views, but most of those are reused across this and other forms.

This is already pretty convenient. But SwiftUI debuted alongside Combine, Apple’s framework for “processing values over time.” In practical terms, Combine lets you easily, accurately keep track of everything from network request progress to the state of your user inputs, consistently reflecting that state in both your UI and your data model.

These two frameworks together make it ridiculously fast to build a UI. This operated on two levels:

  • Easy to experiment/prototype and quickly see results
  • Less time spent on debugging because things largely just worked

Building Tallymander from scratch, in Swift, I started out with UIKit and thought I might experiment with SwiftUI. It seemed like a good way to build detail views and forms.

By the time I finished, 95% of the app’s view code was SwiftUI. It was just so much better at solving the problems I was tackling, and so much more fun. Before bed, I’d imagine some new feature I wanted. With SwiftUI could often knock it out within an hour or so the next morning.

The almost-instant previewing of SwiftUI interfaces was a big factor here. Rather than building and launching to check sizing, typography or content, you can see in seconds whether your layouts and drawing code are doing what you expect. One thing that surprised me was how deep these previews can go. You can include anything in your application as input to a SwiftUI preview, and that includes Core Data entities. Set up a garbage context that writes to /dev/null and you can build arbitrarily complex object graphs to test your designs.

SwiftUI’s layout system takes some getting used to—I’d be lying if I said I’d come close to mastering its more advanced features. Still, the basics are so easy to learn, and get you quickly to a UI that easily scales to any screen size. It’s a big improvement on the fiddly autolayout experience of yore.

I was helped along the way by Thinking in SwiftUI. To give you an idea how early days we are, I started reading it while it was available chapter-by-chapter on a pre-release basis. It’s done cooking now and worth every penny. It’s a helpful guide to building a new mental model after years with UIKit.

One thing I appreciate about SwiftUI is how easy it is to construct novel UI components. Example: The reset control on a counter expands to cover its neighbor and present a confirmation message.

Could I have built this in UIKit? Of course. But I’m not sure I would have thought to try, because doing it would be so fiddly. SwiftUI’s state management is so tidy, it’s trivial to add a @State variable, attach it to a few views, and see how you like it. Putting things into layout based on state is as quick as an if statement—so is pulling them out. Tacking on an animation is simple, too. You can go from “what if…?” to working results in a couple of minutes, and then decide from there if you want to keep those results. More experimentation, more fun.

It’s a brand new technology, so there are occasional hiccups. Passing numerical values to TextFields—critical to any math-centric application—is a little buggy, and requires a workaround. But even with these bumps in the road, it’s a much better path to shipping.

Everything in the new Tallymander is drawn with SwiftUI. Aside from the icon assets, there are no pre-rendered images. This is an approach worth taking even in the old world of Quartz and UIKit. With multiple screen densities, and now Dark Mode, if you can afford to do your drawing in code, you’ll save yourself a mess of image variations. SwiftUI makes the economics of this so much more favorable, as you get a realtime preview of your efforts as you go, and the drawing API is significantly less verbose.

But what about iOS 12?

I say don’t even worry about it. My policy has been to target only the latest major OS version for new apps. iOS update adoption is aggressive, especially in the age of emojis. >90% of customers end up on the latest major release within six months, so why bother doubling your test burden? A small and shrinking slice of the market isn’t worth the headache of backwards compatibility in most cases.

This case is even more dramatic now, where it’s a choice between SwiftUI’s velocity and slogging through the mud of UIKit.

Old friends

I said 95% of Tallymander is SwiftUI. So what about the rest?

There was one place where I needed classic, UIKit-style control over the interface down to the quantum level: the actual table that houses your Counters and Calculators.

Right now, SwiftUI doesn’t expose a lot of customization for its analog to UITableView, List. But since this was going to be the core of people’s day-to-day experience of Tallymander, I wanted to be able to tweak everything about layout, cell rendering, edit behavior, you name it.

What’s wild about this, though, is that while the enclosing cells and tableview are UIKit, all of their content is SwiftUI. It’s possible to make them work together that seamlessly. Watch:

The Counter buttons that fade away, along with the bandage symbol, are all views in SwiftUI. They animate alongside the red delete controls on the left, and the reorder controls on the right, which are provided by UIKit.

But to the user, these details are invisible. Thus, where you need an escape hatch to UIKit or SwiftUI in your project, you’ve got one, and there’s no compromise in user experience. That’s powerful stuff.

Other modern niceties

Working with Swift is a joy. The rigid type system took some getting used to after years of Objective-C. But the reward for this temporary discomfort is fewer fucking bugs. According to TestFlight, Tallymander has never crashed in the wild. Not once. The only crashes I’ve observed were the immediate consequences of doing weird things with text that SwiftUI didn’t like.

This is a lot of time back in your pocket, once again, because you’re working on features instead of chasing bugs.

I also want to take a moment to celebrate a small feature in iOS 13. SF Symbols debuted at the last WWDC and it’s a game changer for teams large and small. By providing a vocabulary of 1500 symbols, developers now have a low friction way to enhance the communication of their UI. This was an enormous headache when I was starting out in 2008. The OS was filled with symbols that meant things—chevrons as “disclosure indicators,” little red gems to indicate delete controls, green dots for creation controls.

SF Symbols: Apple provides a Mac app as a directory of all the glyphs

If you wanted to put your own spin on these, you had to build them from scratch. If you needed your own symbols, you needed to design or procure them as well. An entire cottage industry formed around this—I supported the Glyphish Kickstarter because it promised a few hundred such symbols.

There was also friction with getting such images to the screen. You had to size and convert them, add them to your Xcode project, and with the advent of Retina displays, manage multiple resolutions.

With SF Symbols, it’s a one-liner: Image(systemName: "chevron.right"). It’s trivial to style your symbols with colors appropriate to your app design. If you’re using SwiftUI, composing them with other UI elements is a snap using ZStack and friends.

This makes it so much faster and easier to make a UI that’s clear and easy to understand. Because you’re using a shared vocabulary with the OS and other apps, users will feel at home with your iconography.

Open for business

With SwiftUI, it’s a great time to build iOS apps. The time costs are lower than ever, the APIs are fun and modern, and the results are satisfying. The new technologies have their quirks, but have already gelled to an impressive degree with less than a year in developer hands. I know this was very high-level, but rest assured I have a few practical tips I’ll be sharing soon, especially about how to make UIKit and SwiftUI work well together.

I hope you’ll check out the new Tallymander and share it with anyone you know who’d enjoy a tool like this.

I’ll be continuing to flesh out Tallymander, and plan out my next projects. If you need support for an iOS project on a freelance basis, drop me a line. I can give you the perspective and experience of someone who has not only been with this platform from the beginning, but who is also up to speed on the latest technologies.

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.