Getting back into it

May 9th is Grocery's 6th birthday, and there's a lot to celebrate. The app has almost a million downloads and continues to be used by more people year over year. I'm so thankful to everyone who has tried the app and taken it shopping. Grocery has surpassed all of my original expectations and continues to be the project that I am the most proud of.

Usually a Grocery blog post would be all about a new update I'm shipping to make the app better, but not this time. I am actually working on several updates to Grocery, so keep a look out for those coming soon. But I wanted to take some time to reflect on a different kind of story about the updates I haven't shipped, and why that’s been especially challenging for me as a solo app developer.

On January 16th I underwent a spine surgery procedure known as an Anterior Lumbar Interbody Fusion (ALIF). If that sounds familiar, it’s the same surgery that Tiger Woods had a few years before his most recent victory at The Masters. I underwent the surgery as the last resort to try and resolve chronic low back pain that I’ve been dealing with for many years.

Dealing with chronic pain is extremely challenging and confusing. I really didn’t know what to do when my lower back started to hurt five years ago. I tried stretching, yoga, and physical therapy. I tried improving my posture. I tried a standing desk. Back pain is extremely common but it’s usually mercifully temporary. But in my case it just got progressively worse until it was too painful to sit in any kind of chair for more than an hour.

I wish I knew what the cause was. There’s no specific injuring moment I can point to that caused my pain to start. When a runner tears their ACL there's no mystery involved. They feel the exact moment that the injury occurs, and there’s a clear path to follow immediately afterwards to resolve the issue. But you don’t expect to tear your ACL sitting at your desk, and I certainly couldn’t imagine doing anything to injure my back severely enough that I couldn’t sit anymore.

As it turns out though, my back was injured. The lowest lumbar disc was essentially gone, and two of my vertebrae were no-longer aligned. That caused my spine to be unstable and arthritis was setting in. That’s not a great situation to be in for anyone, and it was rendering all of my other efforts to relieve the pain moot.

I figured all of this out last year, when the chronic pain became effectively permanent. Sitting at my desk for more than an hour was extremely uncomfortable, which was starting to make things harder for me at work, and was impacting the time I could spend working on Grocery too.

It’s safe to say that my pain was affecting nearly all aspects of my life, but where I felt the loss most acutely was in my work. Even the temporary loss of something that has become your life is tough to deal with. I know that for athletes who are injured, their sport and their training are what they miss the most. For me as a software engineer who is injured, I know that sitting and feeling productive writing code is what I miss the most too.

Back pain and spine surgery can affect anyone in any field, but I haven't seen many people talk about what it's like to deal with as a solo app developer. I could write a book about how back pain has affected other aspects of my life that nearly anyone could relate to. But I think Grocery is the most interesting part of my recovery journey because it’s something that I can’t step away from; when I do step away from Grocery there’s no one else who can step in.

There’s a lot of great things about being the solo developer of an indie app. You are in the ultimate position of flexibility. You get to build exactly the app you want, with exactly the technology you love, on whatever schedule you choose. You get to wear all the hats, write every line of code, and enjoy all of the satisfaction knowing that people love using the thing that YOU made!

But there are some less great things about being a solo developer too, and a big one is that when you’re injured, having major surgery, and recovering from surgery, no one else is developing your app for you. I think that it’s pretty common for solo indie developers to take breaks from writing code - no one should be writing code nonstop all of the time anyways. But I think that those breaks are usually because it’s just time to do something else for a while, not because of an injury that forces you to step away entirely.

When my back started to get pretty bad last year I was in the middle of Grocery’s biggest release ever: 3.0. I think it’s worth celebrating that I actually did ship such a major release for Grocery while dealing with all of this, because it wasn’t easy. A lot of my time working on the app was done laying down on the couch or sitting in a chair with an ice pack to reduce the pain in my back. I was motivated to finish the work though because this release was a big deal. The complete design overhaul and new features really felt like they put the app in a great place. Seeing that work taking shape gave me the motivation to push through the pain and get it done.

One of the key features of 3.0 that I added at the very end was a completely re-written User Guide. Looking back, I think that was the most important feature of the release, and going forward will continue to be a major area that I invest time in. While I was working on 3.0 I spent a lot of time fixing bugs that users had reported, and trying to resolve confusing user experience issues that I heard about in emails. I tried to capture anything that I thought users would need to know about the app in the user guide so that people would have a place to go to look for answers if I couldn’t respond to support email.

That would prove to be prophetic, because it’s been very difficult for me to respond to support email during my surgery recovery period. The way that I usually handle support email for Grocery is that I’ll take a Saturday or Sunday here and there to respond to everything I’ve received in the last few weeks. That takes a few hours though and I just couldn’t spend that kind of time in front of a computer after my surgery. As a result, my support responses have been much slower and I know there are questions that my users are waiting for answers on. But because I spent time filling out the user guide, I know that there is at least a place for users to find answers to most of what they should need to be able to use the app. It’s not a replacement for responding to support email, but at least email isn’t the only avenue for users to get answers.

If I had to do this whole surgery thing over again though, I would probably look into hiring someone to help part time with support. It’s a tricky problem because some support requires code level knowledge that another person wouldn’t have. But it would be better than not being able to do anything myself, which is where I was after surgery for several months.

Throughout this period I’ve had to remind myself that the reason normal people are using an app is for what the app does right now. People aren’t looking at my roadmap and deciding if they should use Grocery based on that. I feel extremely thankful that the app was in a pretty good spot before I went under the knife, but that doesn’t stop me from feeling guilty and sad that I’ve made no progress on the improvements and new features I want to work on.

There’s a physical side of combating any injury and there’s also a mental side. They are equally important and in a lot of ways the mental part is the toughest to handle. When I’m sad about the lack of progress that I’ve made on Grocery during this period I’ve found it helps me to focus on _why_ I am doing this. Why am I working on this app in the first place and what am I hoping to get out of it?

I recently read a support email from a husband and wife that love using the app to shop. I recently used the app myself to plan and cook a whole meal of enchiladas from scratch for my partner and family (the first time I’ve done that since before surgery). The love for Grocery is still there because I know it has a place in my life and the lives of many others.

Recovering from an injury requires a singular focus on getting better. A dear friend has been helping me focus on each of the incremental milestones I achieve during recovery. The first time I walked around the block, made my own meals, drove a car, went to a restaurant, watched a movie out of bed, played a computer game, and went back to work. We call them medals. I didn’t earn very many during those first few weeks but over three months later and I’m earning a few every week. Progress is definitely happening and it’s critical to celebrate it.

I’m nearly back to where I was before surgery, and I have a lot of recovery left to focus on to get my back to where it needs to be. I think I’m also nearly ready to focus just a little more on the work that I would like to get back to with Grocery. I think that earning a few Grocery medals might just give me some of the motivation I need to prepare my mind for working on the app regularly once my body is ready to do so.

I’ve always liked to start small when I’m trying to get back into the swing of anything, so I figured my first step coming back should be something simple like fixing a bug. Thankfully, I just checked that one off. Two minor issues were reported via email, and both turned out to be very easy fixes. Medals awarded! First Grocery code changes, bugs fixed, and support emails responded to since Surgery 💪.

I’m hoping to continue checking off a few more smaller medals while working towards some larger ones: clearing out my support queue (earned 🥇), adding a small requested feature (earned 🥇) and shipping my first bug fix update after surgery (coming soon 🏆). I don’t have a timeline for the release but I know that I’ll feel really good about completing it when I do.

I’ll be looking for other ways to build excitement and motivation along the way. The biggest excitement booster I can think of has always been WWDC in June, and I won the ticket lottery so I'll be there in person. If you're a developer reading this and you'll be in town, reach out! I would love to say hi. I've always come home full of excitement after WWDC and I think it'll be just what I need this year too.

This injury, surgery, and recovery is up there with any major challenge I’ve faced in my life. This experience has been a great reminder that there are some things you just can’t do alone. I know that I have benefited greatly from the love and support of my family and friends, and honestly don’t think that I could have recovered without them. I’ve needed to be realistic with myself that I can’t do all of the things I would like to, yet determined to progress back to a place where I can do those things eventually.

That I think is the key for solo developers in a situation like this. Be patient with yourself and deal with what life is throwing at you, while persistently knowing that your app will be there for you when you’re ready.

Adding Services to Grocery for Mac with Catalyst and AppKit

The two things I was most excited about while sitting in the audience for the WWDC 2019 keynote were the hotly anticipated encore for the Mac Pro tower computer, and the hotly debated introduction of the Catalyst framework for building Mac apps with UIKit. As a life long Mac user, and previous owner of earlier Mac towers, the introduction of the newest (and perhaps final) giant tower Mac was super exciting. But in some ways, the introduction of Catalyst was more meaningful to me personally as an engineer. Even though I've used a Mac for my entire life, and been programming since high school, I had never built an app for the Mac before.

So when I set out to build a version of my app Grocery for the Mac I knew from the beginning that I wanted it to be as Mac-like as possible. That meant menu bar menus full of keyboard actions, arrow key navigation for lists, right click context menus, expected features like open recent files, and adopting the Mac look and feel with translucent sidebars, a native Mac layout, and supporting system Dark mode and accent colors.

Honestly, getting Grocery to work the way I wanted it to with Catalyst was a lot easier than I thought it would be. Almost all of the work necessary was work I already had to do to build a great iPad app. Everything past that point, like supporting the menu bar, translucent sidebar, accent colors, dock actions, file open menus, etc. fell into place one piece at a time in a fairly orderly fashion.

But this isn't actually an article about Catalyst itself. Rather, this is an article about the final step in the process to Mac-ass the hell out of Grocery by publishing actions to the Mac services menu.

That step started when I re-read an excellent article by Steven Troughton-Smith called Beyond the Checkbox with Catalyst and AppKit. I had read the article over a year ago, and in hindsight it's incredible to me that Steven wrote this literally the first week that Catalyst was announced. Despite reading it a few days after he wrote it, I really needed to go through the process of getting my app ready for the Mac before I was able to really appreciate the additional power it contains. Here's what it let's you do.

What Steven describes in that article is how developers can create an AppKit plugin for their UIKit app running on the Mac. Normally, the UIKit part of a Catalyst app has no access to AppKit and thus can only interact with the parts of the Mac that Apple exposes through the Catalyst layer. But sitting behind all of that is AppKit itself, and the plugin system is the key to unlocking it.

The process of creating a plugin has a few hoops to jump through. The plugin is a bundle that is built and embedded in the target app at compile time. The bundle contains essentially whatever code you want it to. I called my bundle "Catalyst Bundle" and called its primary class "Catalyst App".

class CatalystApp : NSObject, CatalystDelegate {
    weak var callback : CatalystCallback?
}

The next step of the process is to actually load the bundle in your iOS app so that you can use it to talk to your plugin. Our bundle knows the name of its principal class, and also knows some information about its type (thanks to the Objective-C runtime). In my case, my principal class is a subclass of NSObject that conforms to an Objective-C protocol called "Catalyst Delegate". That way, I can create an instance of my principal class and cast it as a Catalyst Delegate. By storing a reference to that object in my app, I can treat it as a delegate to access functionality provided by the plugin.

class CatalystBridge {
    static func loadDelegate() -> CatalystDelegate? {
        #if targetEnvironment(macCatalyst)
        guard let url = Bundle.main.builtInPlugInsURL?.appendingPathComponent("Catalyst Bundle.bundle") else {
            return nil
        }
        guard let bundle = Bundle(path: url.path) else { return nil }
        guard bundle.load() else { return nil }
        guard let catalystBundle = bundle.principalClass as? NSObject.Type else { return nil }
        guard let delegate = catalystBundle.init() as? CatalystDelegate else { return nil }
        return delegate
        #else
        return nil
        #endif
    }
}

One other note about the bundle and protocol. I tried creating a Swift protocol of various forms (conforming to Class, NSObjectProtocol, etc.) but I was not able to get any of those protocols to work like this. Therefore, I resorted to declaring the protocol in Objective-C and including it in an Objective-C Bridging Header in both the plugin and my app target.

@protocol CatalystDelegate <NSObject>

- (void)setCallback:(nullable id<CatalystCallback>)callback;
- (void)setProxyIconFileURL:(nullable NSURL *)url;
- (void)setDockTileImageURL:(nullable NSURL *)url;

@end

Steven's article talks about all of the crazy things that an AppKit plugin like this lets you do. The first ones I tried involved overriding NSApplicationDelegate lifecycle methods. I wanted my app to quit after you closed the window: there's a delegate method for that. I experimented with changing the Dock tile, adding Dock menu actions, and adding a proxy icon in the title bar for the open Recipe file. I'm not shipping any of those changes yet because I didn't get everything to work the way I wanted to, but I am shipping the last thing that I tried which is publishing Services actions to the Services menu.

Services have been around on macOS X since the beginning, or close to it, but I only started using them personally fairly recently. Services are actions that operate on a type of content. The most common content types are text, images, files, and website URLs. I've created several services that operate on images and files as productivity tools. One of my favorites is a service to convert HEIC images into JPEGs (which should probably be built into the system at this point). I've got a few others for dealing with common types of files I use at work. When I saw Steven's mention of Services at the end of his article I knew I had to at least try it so I jumped in.

Trying to find documentation about how to define a Service action is virtually impossible. The most common search results all pertain to what we think of as "services" today, like iCloud or Apple Fitness+. The next most common search results are for user-facing ways to use Services, like Macworld and iMore articles for power users. The Apple tutorial for Services and this 2017 article by Kelvin Ng were the best documentation that I found but they still weren't enough to get everything working correctly for me. I'll try and fill in some of the missing pieces below.

A service definition starts with an entry in the Plist file. This is certainly where the bulk of Apple's documentation focuses, and most of it is pretty helpful. Under the NSServices key there's an array of service dictionaries. The most important keys in each service are the NSMenuItem, which defines the title of the service, the NSMessage, which defines signature of the method that will be called to activate the service, and the NSPortName, which is just the name of the app.

The other 3 keys that I made use of are the NSRequiredContext, NSSendTypes, and NSReturnTypes. The first lesson I learned is that NSRequiredContext needs to be defined with an empty dictionary before the service will appear at all - so that's step one. NSReturnTypes is only required if you plan on building a service that modifies data and sends it back, so if your service is only used for sending data from another app into yours then you can ignore that one.

<dict>
    <key>NSMenuItem</key>
    <dict>
        <key>default</key>
        <string>Add Items to Grocery List</string>
    </dict>
    <key>NSMessage</key>
    <string>addToList</string>
    <key>NSPortName</key>
    <string>Grocery</string>
    <key>NSRequiredContext</key>
    <dict/>
    <key>NSSendTypes</key>
    <array>
        <string>NSStringPboardType</string>
    </array>
</dict>

NSSendTypes was the trickiest for me to figure out. For text based services you only need to put NSStringPboardType in here. But I had trouble finding the right send types for a service I wanted to make that operated on files. I tried a few things that didn't work, but in the end I ended up checking the Info.plist file for BBEdit to see how they defined their "Open File in BBEdit" Service. They provide both the public.file-url and public.utf8-plain-text send types, which is what I ended up using for my Service (and so far is working great).

<dict>
    <key>NSMenuItem</key>
    <dict>
        <key>default</key>
        <string>Send Recipe to Grocery</string>
    </dict>
    <key>NSMessage</key>
    <string>importRecipe</string>
    <key>NSPortName</key>
    <string>Grocery</string>
    <key>NSSendTypes</key>
    <array>
        <string>public.utf8-plain-text</string>
        <string>public.file-url</string>
    </array>
    <key>NSRequiredContext</key>
    <dict>
        <key>NSTextContent</key>
        <array>
            <string>FilePath</string>
        </array>
    </dict>
</dict>

Back in my Catalyst App plugin class, I have a function that gets called after the app starts up that registers itself as the NSApplication's servicesProvider. By registering as the services provider I'm telling the system what class to send messages to when a service is activated.

extension CatalystApp {
    func setCallback(_ callback: CatalystCallback?) {
        self.callback = callback

        NSApplication.shared.servicesProvider = callback == nil ? nil : self

        NSUpdateDynamicServices()
    }
}

That also means that I need to implement methods on my Catalyst App class for each of the services that I defined in my Info.plist file. The method is named with the value I provided in the NSMessage key of my Info.plist file, and is passed 3 parameters: an NSPasteboard, a userData string, and a pointer to an error that can be displayed in the console if something goes wrong.

Since these have to be Objective-C compatible methods for the services system to send messages to them, I had a few hoops to jump through to implement them in Swift. Here's an example of what one of them looks like for the "addToList" Service that adds the selected text content to the grocery list.

@objc func addToList(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) {
        if let string = pboard.string(forType: .string) {
            callback?.didReceiveAddItems(toList: string)
        }
    }

Getting data out of the pasteboard and putting it back into the pasteboard is pretty much the same as it is in UIKit. The only two issues I ran into here were that the string representation of a fileURL needs to actually be converted into a real URL using the URL(string:) initializer before you can read it. Otherwise it's not useful.

@objc func importRecipe(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) {
        if let path = pboard.string(forType: .fileURL) {
            callback?.didReceiveAddRecipe(toGrocery: path)
        }
    }

The other issue was that adding items to the clipboard using setString: didn't actually do anything. I had to call clearContents() on the pasteboard first and then use the writeObjects method to add content back before it would actually replace my selected text in another app.

@objc func cleanUp(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) {
        if let string = pboard.string(forType: .string) {
            let returnText = callback?.didReceiveCleanUpRequest(with: string) ?? string

            pboard.clearContents()
            pboard.writeObjects([returnText as NSString])
        }
    }

The final piece of the puzzle for me was creating another delegate protocol for my plugin to communicate back to my iOS app to tell it when the Service received a new request. I created this as another simple Objective-C protocol to share with the same header file that I used earlier, and made my iOS app's app delegate conform to it. Now my plugin can send action requests and lifecycle events back up to my iOS app that it can pass through the appropriate methods that it already has access to. That works out nicely because all of the Services I created already map closely to NSUserActivity-based Shortcuts that my app supports, so the necessary code to handle them already exists.

@protocol CatalystCallback <NSObject>

- (void)didReceiveAddItemsToList:(nonnull NSString *)text;
- (void)didReceiveAddRecipeToGrocery:(nonnull NSString *)path;
- (void)didReceiveCreateRecipeWith:(nonnull NSString *)text;
- (nonnull NSString *)didReceiveCleanUpRequestWith:(nonnull NSString *)text;

@end

The end result for Grocery is 4 different Services that provide access to some of the most important features of the app:

  • Add Items to Grocery List - add selected text separated by newlines to the current grocery list
  • Clean Up Items using Grocery - modify the selected text using Grocery's CleanUp feature to prettify text
  • New Recipe with Selection - start a new recipe with the selected text
  • Send Recipe to Grocery - import a plain text file containing a Recipe into Grocery
Screen Shot 2021-02-24 at 7.58.18 PM.png

Unmodified Text

Screen Shot 2021-02-24 at 7.58.39 PM.png

Selected Service

Screen Shot 2021-02-24 at 7.58.46 PM.png

Cleaned Up Text

I'm really happy with how these Services turned out. I doubt this is a feature that many users would have asked for, but for me that wasn't the point of trying to build it. I just wanted to prove to myself that it was possible to build something supremely Mac-like in my Catalyst-based app, and I'm very happy to report that it is. And even more importantly to me, this brief little forray into AppKit has me even more excited about continuing to add more Mac-ness to Grocery in the future so that it will continue to be as Mac-assed as it possibly can be.

CloudKit Sharing for Grocery Households

Soon after Ryan and I released Grocery, our smart shopping list app for iOS and watchOS, we had a decision to make. We were already planning to add new features like support for recipes, so we had to decide what our app was going to be. Would Grocery always be a shopping list app, or would it become something else?

We decided then that Grocery would first and foremost be a shopping list app, and we don’t expect that to change. We’ve always kept that principle in mind while adding new features like recipes and later inventory tracking. We want anything we add to Grocery to make shopping better.

With that in mind, I’m really excited today to be launching a major new feature to Grocery that we call “Households”. We’ve been building towards Households for over a year now in order to support sharing almost all of Grocery’s features between family members.

Households enables shared sorting data, inventory status, and meal plans.

Households enables shared sorting data, inventory status, and meal plans.

Grocery actually launched 3 years ago with support for shared shopping lists through Reminders. Since Grocery keeps list content in Reminders, users can invite other people to a shared Reminders list and see all of the same content in their Grocery lists. That feature still works great and isn’t going anywhere, but we know that a lot of people want to share more than just the Grocery list content with their Family. That’s why we built our Households feature.

Creating a household allows families to share a common set of meal plans (planned recipes), stores (sorting order and shopping history), and inventory status (when things were bought and when they expire). All of this is made possible by Apple’s CloudKit Sharing feature which lets the household creator invite other people to join the household. That way, no one needs to create accounts or sign in with any kind of third party platform. Everything just works as long as everyone uses iCloud.

Grocery’s key feature is our smart sorting system that learns how to automatically sort your shopping list based on the order you shop in your stores. Sometimes people want to share the same sorted list order with other people in their family. If one person is creating the shopping list for another person to shop with, getting that list in the right order can help the person shopping be more efficient at the store. That’s a more important feature now than ever before, when people need to minimize the amount of time they spend in the grocery store.

When people are in a household together they’ll start seeing these kinds of behaviors:

  • When one person adds a recipe to the shared meal plan, everyone in the household will see that recipe

  • When two people are viewing the same store, the items will be sorted in the same order

  • Everyone in the household will get the same set of autocomplete suggestions

  • Everyone in the household will see the same purchase history and frequently purchased items

  • Everyone in the household will see the same in-stock items in the household’s inventory

The two behaviors that Households won’t cover at launch are Reminders list sharing and recipe folder sharing. Grocery still uses Reminders for the actual shopping list content, which means we still rely on the system Reminders app for actually sharing the Reminders list with other people. When people in the same family share a Reminders list from the system Reminders app, each person can see that shared Reminders list on their iOS device and in Grocery. 

We are planning to introduce our own version of iCloud shopping lists as an alternative to Reminders at some point in the future, but for now people that want to share list content still need to use the system Reminders app to invite people to shared lists.

We are also also planning to eventually support recipe folder sharing via the newly released iCloud Folder Sharing API, but since that feature just shipped we’re planning to wait a little while before adopting it fully.

We’ve found that sharing meal plans, shopping history, and inventory are helpful to how we use Grocery. When everyone in a household gets the same autocomplete suggestions, you get more consistently named list items. When everyone can see the meal plan, it’s easier to share recipes amongst people and to see what’s for dinner. When everyone can see the pantry contents it’s easier to answer the “should I add this to the list?” question before shopping.

Members of a household also gain access to all of Grocery’s premium features, so creating a household is a way for a family member to share Grocery Premium with everyone in their family without requiring everyone to buy their own premium subscription.

Household syncing is completely non-destructive. When multiple people join a household, everyone’s data is added to the shared household. If someone leaves the household they retain a copy of all of the data.

From a developer standpoint, building Households with CloudKit was actually a great experience. The mental model for sharing is all about parent references. When someone creates a household, all of their grocery data starts using that household as the parent record in CloudKit. That means all of their data needs to be uploaded back to CloudKit with the new parent reference, which can take some time. When you’re setting up Households for the first time it might seem like it’s going to take forever. I definitely wish it’s as faster to setup, but once it’s complete, incremental syncing is incredibly fast, with changes appearing on different devices usually within seconds. That’s a testament to how well the CloudKit subscriptions feature works which is what makes the real-time nature of this possible.

Since this release really is all about shopping we did also take time to fix several key bugs related to shopping and list sorting. We fixed two very hard to reproduce issues where the sorting history of some items could be forgotten, and an issue where adding new items to a manually sorted list could have strange consequences.

Last but not least, Grocery now supports PRINTED shopping lists! This feature has also been on our todo list since the beginning, and this felt like the right time to introduce support for it since having a printed, disposable, shopping list can actually help people stay safe while shopping at the grocery store.

Households are a big step forward for us and we’re excited about the potential. We’ll be continuing to improve how Grocery can be shared amongst families in future releases as well.

Writing Recipes in Grocery

Grocery first launched with a major improvement to how grocery lists are automatically sorted. The app remembers the order that items are checked off and maintains that order the next time you shop. That's a major improvement over manual sorting, and over beacon-based sorting systems that rely too heavily on the user's location inside of a building and are only available at a handful of stores.

Grocery 2.0 represents a major improvement to how recipes are displayed on iPhones. The app lets users optimize their recipes by placing the ingredients next to the steps they're used in. That's a major improvement over traditional recipe apps and websites that structure a recipe as a block of ingredients and a block of steps. The screen isn't big enough to show the entire list of ingredients alongside all of the steps, and so you end up scrolling back and forth between steps and ingredients to see how much of something you need to add for the step you're working on. It's very inefficient and it's also easy to get lost and forget a step or ingredient. It's not a great cooking experience.

Markdown is what we're using to make this possible. Our recipe format uses a subset of markdown to represent key components of a recipe. When users create recipes in Grocery, we're adding each ingredient, step, header, and note as a separate line to a markdown text file in the order they specify. We've found that to be all the flexibility required to write very intuitive and easy to follow recipes that work really well when displayed on iPhone!

Of course, that flexibility also allows users to structure their recipes in Grocery the traditional way, with a block of ingredients and a block of steps. Indeed, if you try to copy a recipe from a website into Grocery that's essentially what you'll end up with. Optimizing the recipe into sections with their associated ingredients and steps takes a little bit of work, but I think it results in a much nicer recipe that's more fun to cook with.

Before Grocery 2.0 launched I went through all of my recipes that I had saved and added them to Grocery. I had recipes saved in other apps, bookmarked, sent to me in emails, and saved as photos from family recipe books. I went through each one and optimized it for our new format. The result is a personal recipe book that I hope to keep forever. That feels like a realistic goal when each recipe stored as a markdown file that can be opened by any text editor!

One example of a recipe I added is my mom's Fudge recipe. This is a recipe I remember helping her make for years and something I never want to forget. The original recipe was handwritten on a piece of paper. As expected, all of the ingredients are at the top and the steps are at the bottom because they all fit on one piece of paper. But they won't all fit on one iPhone screen so we need to figure out what order to arrange everything in while adding the recipe to Grocery.

Original fudge recipe

Original fudge recipe

Making Fudge starts with melting together the butter, sugar, and evaporated milk, so that's the first section in the recipe. Those three ingredients are the first things in the recipe, followed by a step that describes what to do and includes a 5 minute timer.

Fudge Recipe in Grocery

Fudge Recipe in Grocery

I was actually home with my mom while adding this recipe, and we remembered that Fudge requires heating the mixture to a specific temperature, but we couldn't remember what that temperature was. After looking it up and adding to the recipe we also decided to add a note to the recipe with a link to where we found that temperature! Now we'll have that as reference if we forget again.

The next steps are mixing in the chocolate, marshmallow cream, and vanilla, followed by nuts if desired. I added each of these to the recipe as ingredients or steps, specifying the amounts for each ingredient. The result is a recipe for making delicious Fudge that's easy to follow on an iPhone!

Checked off ingredients and steps for making fudge

Checked off ingredients and steps for making fudge

Grocery's recipe view includes a few other features to help make sure you'll never forget an ingredient or a step. Tapping on an ingredient or step while cooking checks off that cell in the viewer, marking it as completed. That state is preserved until the recipe is finished cooking so you can go browse other recipes or quit the app and still come back to the recipe later.

Efficiency with cooking paired with efficiency for shopping makes for an amazing cooking experience. I've always loved cooking but working on Grocery and adding these recipes has made me more passionate about it than ever. I hope you try it out and let us know how you like the recipes you create!

A Recipe for Building Grocery 2.0

The story of Grocery 2.0 begins around Christmas of 2017 with a family recipe book.

Grocery 2.0

Grocery 2.0

Ryan and I were back home with our families looking up recipes and planning holiday meals. After picking a recipe to make you typically add the ingredients to your grocery list and go shopping. We both wanted to have our recipes in Grocery to make adding those sets of ingredients easier.

I think it's important that this realization happened with family recipes because family recipes are very special. To many people, they’re as special as family photos: passed down generation to generation. If we added recipes to Grocery it would need be a great recipe feature that we could trust with those recipes. We set out to do just that and started design in January of 2018.

The obvious next step after design would have been to hit File -> New View Controller in Xcode and start adding a recipe screen to Grocery. Instead, we created an entirely new Xcode project to iterate quickly on our recipe concept. Our goal was to prove that our design worked and bring what we learned back to Grocery and quickly ship our recipe feature.

We eventually did bring that recipe feature back to Grocery and we’re excited to be shipping it in the app today! But along the way, we also re-wrote virtually all of Grocery’s UI in a way that greatly improves the consistency of elements throughout the app, as well as how code is structured for re-use across screens.

One way to look at it: Version 1.0 was basically our prototype. Version 2.0 is the real deal.

That probably sounds weird if you've already been using 1.0 and love it. We love it too! But for multiple technical and user experience reasons I can safely say that 2.0 is a huge improvement, not only to the experience of using Grocery but also to its technical future for many releases to come.


What’s the deal with a new Xcode project?

I initially resisted adding recipes to Grocery because I was in love with the idea that Grocery is just a simple shopping list. Building recipes seemed incredibly complicated. You need a list to view all your recipes, a recipe viewer for cooking and selecting ingredients, and an editor to create new recipes. You need a place to safely store all of the recipes a user adds, with all of their section headers, ingredients, steps, notes, timers, and photos.

Using a new Xcode project gave us space to work through design and technical hurdles without worrying about breaking Grocery. Once we had the new project we started attacking all of the problems involved with building recipes:

1. Using Markdown as a storage system for recipes

Our entire concept for recipes hinged around using markdown to improve flexibility for displaying recipe content. We wanted our recipes to stand the test of time with an open document format that supports interleaved ingredients and steps.

Testing this theory in the new Xcode project proved that a basic UI for creating recipe-focused markdown files was easy to use for adding recipes. ✅You can read more about the recipe markdown format we arrived at on GitHub.

2. iCloud Drive

Recipes need to be shareable across devices and with friends and family members. But we weren’t sure how well recipes sync between devices if we stored our recipe markdown files in iCloud Drive.

We tested this feature by adding support for reloading a recipe in real time as changes are made on another device, which actually works VERY well! Syncing is surprisingly quick.

3. How to support multi-line text in list cells

Multi-line text has always been a challenging problem because every cell in your list could have a different height.

Grocery 1.0 supports Dynamic Type, which requires some variation in the height of each cell, but we still limited the length of items in the list to a single line. That would never have worked for recipes that usually include long steps that span several sentences.

So instead of a standard table view, we built the recipe screen as a collection view and worked through all of the details to get multi-line text working. Our solution worked really well for recipes, and we even made the solution re-usable so that we could use it in other screens like the recipe list and ingredients list.

4. Themes

We'd been wanting to introduce different app theme colors for a long time. Because why not?

Themes!

Themes!

Building new UI components from scratch in the new project seemed like the perfect time to try it! We ended up with a very clean solution using a notification system that lets each component style itself for the selected theme. We even used Swift enums for all of the theme options!

We had assumed that there would be a new system dark mode setting in iOS 12 that we'd be well on our way to supporting, but that only debuted on the Mac. We'll be ready if that ships in iOS 13 though!


What else did we get from a new project?

Using a new Xcode project to develop recipes helped us solve a lot of problems quickly, but we actually got a lot more out of it than we could have predicted.

We eventually realized that our new Xcode project had essentially become a component-based layout system for building lists that fit our app's style. A component is just a collection view cell with enum-driven customization options like:

  • Title font style

  • Note font style

  • Left thumbnail image

  • Right thumbnail image

  • Full size image

  • Background visible or invisible

  • Attributed text (e.g. for timers and links)

  • Checkmark visible or invisible (e.g. for grocery list items)

  • Disclosure indicator visible or invisible

Example of most of the types of cell configurations that Grocery supports

Example of most of the types of cell configurations that Grocery supports

All of these cells are actually the same type of component-based collection view cell

All of these cells are actually the same type of component-based collection view cell

Each component supports theming and multi-line text for all of its labels right out of the box. Multi-line support also extends to images which are automatically sized to fit the width of the screen with their aspect ratio intact. The components were exactly what we needed to build a flexible recipe layout system, but also worked great for building any other screen we wanted to.


The recipe feature works! Now what?

Our original plan called for iterating quickly on recipes in a new Xcode project before bringing the recipe feature code back into the Grocery project. In my head that essentially meant moving some files from one project to another and linking them together to present the new recipe screens.

There still wasn’t anything wrong with that plan. Grocery was still working well, and we wouldn’t have to make any changes to Grocery before inserting the new recipes feature. There was essentially zero risk to Grocery by going this route. Why would we consider doing anything else?

The other option was to go nuclear and make some major code changes to Grocery itself, while moving the recipes from the new Xcode project back into the main Grocery project. There were a few reasons why this seemed like a great idea:

  • We were way more excited about the UI system we had created in the new project than we were about the UI we had created in Grocery. The code was a lot nicer to work with, and the components fully reflected our entire visual style for the app.

  • By this point we had committed to shipping new app themes alongside recipes, and our component-based layout system already supported theming.

  • We'd been fine tuning the marginal layout in the prototype and it simply felt nicer and more consistent than some of the layout in Grocery. We were building every new screen with these components, and so all of the screens benefited from those improvements.

  • The original Grocery project essentially used different table view cells for each screen. The Grocery List and Quick Add screens had different cells to accommodate one having a checkmark selection and one having a circle selection. There were different cells for settings too, and for lists and stores. This of course caused plenty of issues any time we decided to tweak our layout spacing in the app...which led to us not doing that very often. You never want brittle code to be a reason to avoid change, and our original layout code was too brittle for our liking.


Plot twist

Instead of just moving recipes back into Grocery and calling it a day, we decided to re-build the existing Grocery UI using the new component-based layout system without changing Grocery’s existing model layer.

Grocery List + Recipes

Grocery List + Recipes

We still loved Grocery's internal model and controller layers. The interaction with Reminders, our internal sorting algorithm, Apple Watch, and the Realm database that we use for sorting were all very solid and abstracted out into their own classes.


The Merge

Besides support for themes, we also knew that two of the screens in Grocery needed to be overhauled before our next major version anyway: Stores & Lists, and Settings.

Stores & Lists:

Confusion about the difference between Stores and Lists and how to have items associated with a specific Store was easily the largest source of customer feedback from Grocery 1.0. We'd been brainstorming solutions to that almost as long as we'd been working on recipes!

The solution we arrived at was to merge Stores & Lists into one feature called Stores, and give each Store an assigned Reminders List. That gives users the flexibility to configure Stores however they want to: with a shared list for multiple stores, or a unique list for each store.

New Stores:

The new Stores feature was different enough from the original that it really needed a brand new UI no matter what. The original store/list selection screen just didn’t make sense for the new feature.

Building the new screens with the component-based layout system was incredibly fast! They were easy to build because the components handle all of the layout. The data source for each screen is essentially the a-la-carte counter at a restaurant:

"I'll take three cells with images on the left, titles, and notes on the right; a section header with some top spacing; two cells with some text and images on the right; one cell with two lines of note text; and two cells with tinted titles that make them look like buttons."

Settings:

We re-built settings in the same manner, and that only took a weekend to finish. Because the logic behind most of these screens is relatively minimal, handling all of the layout through shared components cuts out most of the work for adding new screens.

Of course, this component-based layout system only works within the constraints of what our shared components can be configured to do. Adding an entirely new "type" of component still means work. But knowing which components exist and how they can be customized is also really helpful from a design perspective because you have a better sense of what your palate looks like while designing a new screen. If you use those components in the design, you know it's going to be easy to build. It's really a tide that lifts all ships.


The Grocery List Refactor

Grocery is still a pretty simple app. When you exclude the Recipes, Stores, and Settings screens that we already re-built...the only thing left to rebuild is the list screen itself.

I have to admit, the list screen was a little embarrassing to behold. It was actually still called ViewController.swift from when I first created the Grocery Xcode project in January of 2017!

ViewController.swift wasn't quite as massive a view controller as it could have been, in part because of how much logic Grocery had abstracted out into classes that could be shared with the Apple Watch app. But it was still cluttered and disorganized and largely incompatible with the multi-line layout and theming features we were adding in 2.0.

The new list screen is infinitely nicer and actually has a real name: GroceryListViewController.swift!

GroceryListViewController + Extensions

GroceryListViewController + Extensions

Developing the new GroceryListViewController was an incredible lesson in refactoring that I want to describe for you here. This is what the process looked like:

The simple stuff:

Some pieces of the original ViewController had an obvious home in the new GroceryListViewController. If a method is simple and important, just putting it into a more organized place in the new GroceryListViewController or one of its extensions was all that was needed. I saw those pieces as low hanging fruit and tried to move all of them first.

Anything I moved to the new GroceryListViewController I also deleted from the old ViewController.

More complex parts:

Certainly some parts of the original ViewController weren’t needed anymore, or could be better replaced by newer components we’d been developing alongside recipes. After clearing out the low hanging fruit I started combing through the remaining methods following this pattern:

  • Select a method in the old ViewController.

  • Figure out what it's doing.

  • Write a new method in GroceryListViewController that implements the essential features of the old method, generally using newer support systems.

  • Delete the old method from the old ViewController.

As the ViewController file shrank, other files related to the new GroceryListViewController grew. The refactor would be complete when ViewController.swift was empty and could be deleted from the Xcode project.

Drag and Drop:

The most care was needed around features like Drag and Drop. Grocery's original table-view-based drag and drop was incompatible with the new iOS 11 Drag and Drop that supports dropping items from other apps. That sort of refactor required saving the underlying logic of what to do when an item is dropped while supporting a new type of user interaction.

Gaining support for features like iOS 11’s Drag and Drop was yet-another reason that I'm glad we decided to give the grocery list screen a do-over. Eventually the last method was removed from the old ViewController.swift and the file was deleted from the Xcode project. Refactor complete!

Once the refactor was finished, every screen in the Grocery project worked the same way. Having one way of doing things makes the app easier to maintain because there's less context switching across features, and less surface area for bugs to pop up. That by itself can be a very valid reason to perform a major refactoring effort if it means unifying the way core functionality in your app works!


Grocery list, recipes, and back again

A friend at work asked an important philosophical question recently: if you replace every plank on a ship is it still the same ship? Grocery still feels like the same app to me. Sorting the list and adding items still works the same way. Settings carry over to 2.0, with the addition of a few new ones. Quick Add works the same way. But using the app with recipes and themes is so much nicer as a user, and the code base is easier to work with as a developer. I'm not sure if it's truly the same ship or not, but I do know it's a ship I love using and working on!

We're really excited for people to try the new version and hope that you enjoy it. Grocery is free to download and available on the App Store!

Nineteen Step Process to Faster Apple Watch Button Actions

The upcoming release of Grocery focuses on speed. We can all agree that the main thing your app does should be fast, and for Grocery that's checking items off your list. As we added features to the apps the performance of checking items off a list slowed down - a lot. For some users, checking off items could take 3-4 seconds on iPhone, and 5+ seconds on Apple Watch. We needed to address that now before adding more features.

The problem on iPhone was simple to understand and solve using Instruments. When you mark an item off your list on the iPhone, we were waiting for the update to Reminders to finish before moving the table cell. Making that update asynchronous and moving the cell immediately solved the problem. When you mark an item off your list now, the list update happens instantly, just like it should be!

The problem on Apple Watch was harder to understand and harder to solve, and that's the focus of this blog post. Troubleshooting performance on Apple Watch can be tricky. You can try to identify red flags if they exist by running Instruments against the simulator, but the only way to truly evaluate performance on Apple Watch is with the physical device itself. Everything that seems slow on device will feel completely normal on the simulator. You have to test on hardware to solve the problem.

Grocery's Apple Watch app is very simple - just one table view where tapping on a cell checks the item off your list. That's why this particular issue was so perplexing: the app isn't doing anything that seems too complicated. When an item button is pressed, the app sends a message to the iPhone to tell it which item was checked off, and then removes the cell from the table view. The app isn't updating the Reminders database itself, so why is it so slow? Time to investigate!

This blog post describes my nineteen step process to faster buttons on Apple Watch, which is composed of the following individual steps that can be used repeatedly starting with the first one:

  • Disable Everything and Re-evaluate Performance
  • Refactor/Extract Functionality As You Go Into Their Own Methods
  • Start Adding Trivial/Simple Functionality Back
  • Turns Out Some "Trivial" Functionality Actually Hurts Performance
  • Identify Major Problem Areas and Either Improve or Remove Them
  • Put it All Together and Test The Final Version on Device

Step 1 - Disable Everything and Re-evaluate Performance

When tapping on a button feels slow on the Apple Watch the best thing to do is remove everything from the IBAction method and test it again. Button taps should feel as close to instant as possible on the watch. That's a major focus for watchOS 4 with the Unified Process Runtime - making touch interaction feel even faster and more reliable. If you remove everything from your action method and performance returns to normal, then you know something in that method is causing the slow down.

Commenting out the implementation also made me realize just how much functionality had been added to that button press action over time. What started out as a very simple method now included a laundry list of functionality:

  • Update the Apple Watch's Sorting Database
  • Updating the Table Data Model
  • Removing the Table Cell
  • Sending a WatchConnectivity Message to the iPhone
  • Playing a Haptic Feedback
  • Updating the Remaining Items Count
  • Hiding a Placeholder Group
  • Updating the Complication

Sure enough, after commenting all of that out things felt fast again. This approach is also very motivating because you get to see how fast it can feel which makes you want to achieve that performance with the rest of the functionality restored.

Step 2: Refactor/Extract Functionality As You Go Into Their Own Methods

While you're working on the button action method I think it's a great idea to refactor and re-organize the functionality of that method into smaller methods with a single responsibility. As I had been adding features and functionality to that button action I had just been adding them to the same method. I took this opportunity to move each area of functionality into its own method. This has the dual benefit of cleaning up the code as well as making it easier to see what you're turning on and off while evaluating button performance.

Step 3-7: Start Adding Trivial/Simple Functionality Back

I started adding functionality back one piece at a time, beginning with the most trivial pieces that I assumed wouldn't have any impact on performance. I installed a new device build after each piece to test performance on a physical watch. 

Most of the trivial features didn't affect performance at all. Haptics and hiding the placeholder group had no impact. Drawing a strike-through line through the item label with an attributed string didn't seem to hurt. Removing the table cell and removing the item from the array didn't hurt either.

Updating the remaining item count was the first place that I noticed a small change in performance. That involved counting the number of remaining items and updating the Interface Controller's title property. The change was barely noticeable though, so I decided to keep that feature in.

Step 8-10: Turns Out Some "Trivial" Functionality Actually Hurts Performance

The next seemingly trivial feature I added back to my button action was updating the complication. Updating the complication isn't slow on its own, but the way I was updating the complication was triggering a reload from the Reminders database. When I added this method back to my button action performance slowed down considerably. Once that happened it gave me an area to investigate further, which lead to identifying the database reload. By addressing that issue I was able to reload the complication after marking an item off the list without hurting button performance!

Step 11-18: Identify Major Problem Areas and Either Improve or Remove Them

The two major problem areas turned out to be updating the sorting order on the watch, and sending the message to the iPhone to tell it which item was marked off the list.

Updating the sorting order was actually completely unnecessary. In an earlier version of the Apple Watch app we had been moving marked off items down to the bottom of the list instead of removing them. Removing them from the list made more sense because of the small size of the Apple Watch screen. When we changed that behavior we didn't remove the sorting change - which was actually a pretty significant performance penalty. Removing that made a huge difference!

Sending the message to the iPhone using Watch Connectivity made more of an impact than I expected it would. Making that method call asynchronous by calling it on a background queue made our button action feel a lot faster, so that was the only change we needed to make there.

Step 19: Put it All Together and Test The Final Version on Device

Once all of the button action features are refactored, removed, disabled, moved, or improved then it's time to put it all back together and test the final product out to make sure that all the effort actually made a difference in performance. After a few days of testing it's definitely feeling like a big difference.

 

Conclusion

Troubleshooting performance on Apple Watch can be tricky but the effort is well worth it, especially for a device intended for small and quick interactions. It's truly a device where every second counts, and a little bit of testing can help make the difference between an app that feels fast and an app that feels too slow to use. Even with the upcoming improvements to app performance with watchOS 4, anything that we can do to help our apps feel faster will make a big difference for Apple Watch users.

Grocery Version 1.1 Includes Quick Add and Usability Improvements

The reception Grocery has received from users since launching has been great. Several of the reviews really capture why we wanted to make the app:

Couldn't have said it better ourselves, and in that spirit we've been working on some meaningful improvements to the app while staying true to the simple design and focused purpose.

Grocery 1.1 featuring an improved list layout and Quick Add

Grocery 1.1 featuring an improved list layout and Quick Add

The main grocery list interface has received lots of attention. When we released Grocery the title and notes fields were aligned horizontally, which limited the maximum length of titles that could fit on most phones. Stacking the fields vertically removes that limitation and improves readability. It also frees up space on the right side for persistent drag handles to quickly re-order items manually. Grocery learns the order that you shop for items pretty quickly, but it's still helpful to be able to move an item into place if you already know where it should go.

We're also introducing a new feature that we call Quick Add. This feature uses your shopping history to help you remember items you shop for frequently and quickly add them to your list. Quick Add makes suggestions based on how often you shop for repeated items. If you buy Eggs every 6 days, and it's been 5 days since your last trip to the store, then you would expect to see Eggs on your Grocery list. Quick Add learns to recognize those patterns and make adding those items to your list faster.

Grocery 1.1 includes many other general usability improvements:

  • Use custom URL Schemes to open Grocery and add items
  • Improvements to Apple Watch Complication and Snapshot reliability
  • Support for Handoff from Apple Watch to iPhone
  • Alexa can add items to your Grocery list. Check the FAQ for setup instructions.
  • We made a handy text-selection-menu shortcut to "Clean Up" a title by moving extra text besides the title into the notes field.
  • A much requested "Share this app" button to simplify sharing the app with friends and family :)
  • Emoji now correctly appear in the title and notes fields
  • Keyboard shortcuts for iPad
  • Performance improvements for manual re-ordering
  • The number of items in your list are visible at the top of the list
  • Grocery automatically scrolls to reveal the position of new items after they're added to your list.

If you haven't gone for a trip to the store with Grocery yet, give it a try. We think it could be exactly what you need too, but if it's not please reach out and let us know!