Categories
Explainer

Paradigm shift 2020

We’re living through history. Again.

If you’ve had an internet connection at any point in the last three weeks, you know what I’m talking about. But just to underscore what I mean, here’s the New York Times Bestseller List.

Twelve out of fifteen are somehow related to race or racial justice. That is a seismic cultural event. Oluo’s book, in the first position, is two years old. Consensus has converged, in the space of weeks, on race as an essential factor that requires interrogation and understanding.

…America is suddenly cramming for an exam it’s been failing for 400 years.

Renee Graham, via Twitter

Though I’ve worked in a highly segregated industry, and I’ve long hoped to reform it, I’m not an expert on race or racial justice.

Where I do have some expertise is paradigm shift. I’m going to argue that what we are witnessing is a paradigm shift of a lifetime. But before I explain why, let’s sync on what a paradigm shift actually is: how it works and what you get out of it.

Categories
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.

Post-script:

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.

Categories
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 = "markdown.md"

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 = "markdown.md"

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 = "markdown.md"

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.