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!