Saving Previous States with Indirect Enums in Swift

I recently learned a new trick for how to structure enums for switching between states in my app Grocery. I’ve written a lot about how to use enums to manage state in an app so it’s always fun to learn new tricks with enums - especially one like this that I think I’ll be using a lot.

In my app there are several screens that switch between a view mode and an edit mode. In a simple enum those states might be represented simply as view and edit. When the user finishes editing, the time comes to switch the view back into view mode, and so we change our state variable back to the .view case.

enum RecipeParsingState {
    case view(text : String)
    case edit
}

However, things get more complicated if the screen supports more than one type of view state. It becomes difficult to know which case the user needs to return to after editing because that’s no longer inherently defined in the enum structure.

One such example is a text input screen that I’m using in a new recipe importing share extension in Grocery. The screen can be used for selecting a description, ingredients, or instructions from a recipe. Each of those types are cases that the state enum for the screen can be in, since the screen appearance needs to be customized for each of them.

enum RecipeParsingState {
    case description(text : String)
    case ingredients(text : String)
    case instructions(text : String)
    case edit
}

I really only need one edit case for the screen because the editing behavior is the same for all of them. But once I’ve put my screen’s state in the edit case I need a way to figure out which case to return to when the user finishes editing.

I could add another variable to the screen and store the previousState as an instance variable. But this is messy for all of the same reasons that removing Boolean instance variables is important to do in State Driven Development. Wherever possible, it’s better to encapsulate instance state within the context of where it’s actually used.

var previousState : RecipeParsingState?

The solution I ended up with is to add the previous case as an associated value on the edit enum case. That way, when editing is done, we can consult the associated value and return our screen’s state back to the previous case and continue importing the recipe.

indirect enum RecipeParsingState {
    case description(text : String)
    case ingredients(text : String)
    case instructions(text : String)
    case edit(returnToState : RecipeParsingState) 
}

Because the enum is now referencing itself, it needs to become an indirect enum. I hadn’t actually heard of indirect enums before, and the Swift compiler helpfully told me I needed to add the indirect keyword after I included the enum type as an associated value. I did a little bit of research and I haven’t seen any downsides to this structure yet, and the enum seems to behave the same way other enums would except with this new allowance that it can reference itself.

There’s a few screens I can go back to refactor now with this new approach which should simplify how I was managing the states of those screens too. Hopefully this little trick helps in one of your projects too.

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!

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!

CocoaConf Yosemite

When I first heard about CocoaConf Yosemite I couldn't believe there was a conference designed around three very different things that I love: Apple, Photography, and the outdoors. I enjoyed the experience so much that I went back for seconds this year and had an even better time. The conference is returning for 2017, and I'm very much hoping to attend next year.

It's hard not to feel inspired in Yosemite National Park. Spending time outdoors has always been a great way for me to recharge, but the serenity you can experience in Yosemite Valley is quite unique. There really is nothing else like it. If this were just a tech conference then Yosemite would still be a very special setting that people would get a lot out of. But CocoaConf Yosemite is more than that. It's a place to focus on the human side of working in the technology industry and why what we do with technology matters to other people.

When I came to Yosemite last year I spent some time after the conference exploring the park. I had a chance to hike around by myself, which was a unique experience for me: I almost never go out hiking on my own. But it lead to some of the most amazing things I've had happen to me on the trail, like getting snowed in above Yosemite Valley!

Snow! I woke up around 6am with a foot of snow outside my little backpacking tent.

Snow! I woke up around 6am with a foot of snow outside my little backpacking tent.

Getting out and hiking by myself was actually pretty far outside my comfort zone. But that also fits the theme of the conference, which really focuses on learning more about yourself. Pushing myself lead some amazing experiences, including witnessing the most amazing sunset I've ever seen, above Half Dome. A picture of the sunset from Cloud's Rest is my favorite picture that I've ever taken. But it's not just the picture I like, it's the experience that it represents. Talking to people in the valley to get ideas on where to hike to. Planning the hike and determining if I could make it safely to where I needed to go. Being totally isolated, knowing I was the only one witnessing this in person up in the snow, but being able to share the experience with people later through photographs. And knowing the whole time that I was out there doing something new that I enjoyed. Such is the power of exploring the outdoors, that just one sunset can mean so much to you.

Sunset from Clouds Rest, overlooking Half Dome in Yosemite National Park.

Sunset from Clouds Rest, overlooking Half Dome in Yosemite National Park.

While I was exploring Yosemite that first year I had a few books with me. I read Becoming Steve Jobs by Brent Schlender, as well as Creativity Inc. by Ed Catmull. Both books taught me a great deal about leadership that I don't think I would have understood as well outside of a place like Yosemite. The conference and the environment were the perfect backdrop for listening to these types of stories.

This year leadership was a central theme at the conference, as well as another trait I wasn't expecting: vulnerability. I had started reading Daring Greatly by Brené Brown before the conference and I kept marveling at the similarities between what was being discussed in the conference talks and the themes of Brené's book.

Daring to be mediocre. The only thing between you and what you want to do is doing it. Be a little wild because we are in the wilderness. Be your own replacement. Challenge yourself. Having empathy for the point of view of others.

Many speakers told stories of using 31 day challenges as a way to coach yourself into doing something you were interested in but not comfortable getting started with. It takes courage to start something new, especially something you don't know you'll be good at. Daring to be mediocre helps you take a step towards something you want to achieve but are having trouble getting started with.

But the biggest takeaway I had from Yosemite this year was community, and how important it is. Sometimes it's easy to forget in the interregnum between WWDC's just how special the community is around the technology industry. The tech community is one that I think can be of immense service to others. I was so impressed by the talk Christa Mrgan gave on Civil Comments, a tool that is actually helping change people's behavior and positively influence the nature of discussion online. That's the type of impact we can have on the world, and I think it's a perfect example of why CocoaConf Yosemite is so great. It's about inspiring you to make a difference, in your team, in your company, in your community, in the world.

CocoaConf Yosemite is the best experience I've had at a conference. If you haven't been I highly encourage you to go. I'm hoping to return again next year.

Runtime for Apple Watch

When I used to wear a watch on a regular basis, the feature I used most was the stopwatch. I remember timing myself running around a track or around the block in high school and college. Eventually I stopped wearing a watch, but that’s about to change when the Apple Watch ships. I’ll be getting one as soon as it’s available, and I’m excited to also be bringing a version of Runtime to the watch.

Runtime for Apple Watch

Runtime for Apple Watch

Runtime for Apple Watch is a feature rich stopwatch fit for the age of the iPhone. It’s what you would expect a stopwatch to be in the age of the smart watch. Not only can you fully control a run from your wrist, you can see your current location, split times, stats, and an elevation profile all at a glance while you’re out on the trail – without ever touching your iPhone.

The Apple Watch version of Runtime builds on top of Runtime 2.0 which was released for iOS 8. That version included the Stopwatch Widget and advanced statistics support. Both of those features proved to be key stepping stones towards building a great watch app for Runtime.

Runtime’s Stopwatch Widget is a close cousin to Runtime for Apple Watch, but it’s amazing how much more capable WatchKit apps are than Today Extensions. For starters, WatchKit apps can open their iPhone app in the background, so you can start a new run from your watch without ever touching your iPhone. You can also navigate between pages of information on the watch, which is how you swipe between the Runtime stopwatch, current location, split times, and elevation profile.

Another key to building Runtime for Apple Watch is MMWormhole, a lightweight message passing library I worked on at Mutual Mobile. MMWormhole drives everything about Runtime for Apple Watch. It keeps all the statistics up to date and relays all of the start, pause, cancel, and save commands from the watch back to the iPhone.

When WatchKit was announced, I wrote about the limitations of the built in interface timer control. I ended up not using the control in the shipping version of Runtime. The irony though, is that I’m not using a label for the elapsed time either! My friend and designer Ryan Considine came up with the brilliant idea to include the elapsed time inside the start/pause/resume button, which is exactly what I’m doing. Fortunately, MMWormhole is capable of relaying the elapsed time every second to the watch, resulting in an accurate display of information as the title of the button. I’m really happy with how this design maximizes use of the space on the watch.

The elevation profile is really a killer feature on the watch. I had the idea for this while I was out hiking at Big Bend. I wanted to see how far up the ridge I was to get an idea of how much further I had to go. Elevation is key to cyclists as well, many of whom also use Runtime. An elevation profile that shows the elevation of a completed run was already part of Runtime, but there wasn’t yet a concept of the elevation on your current run or hike. I added that to the iPhone soon after, and it’s a huge addition to the watch as well.

The challenge with building the elevation profile on the watch is that WatchKit doesn’t include support for custom drawing like you would do to render the graph of an elevation profile. To work around this limitation, I am rendering a version of the elevation profile graph as an image on the iPhone, and transferring the image over when you view the Elevation page on the watch. This trick is a great way to include rich and detailed information on the watch that you can access quickly without pulling out your phone.

Another new concept to both the iPhone and Apple Watch versions of Runtime are split times. Runtime’s splits are your mile or kilometer times. A split time is recorded automatically every mile or kilometer without pushing a button. Many distance runners use Runtime, and having an automated way to track their mile times is a great way to measure training progress and overall endurance. I think this’ll prove to be a popular feature on both apps.

Glances are a great new interface paradigm on Apple Watch. Runtime’s Glance is very similar to the default state of the Stopwatch Widget, which shows your total step count for the day. I really like keeping track of my daily step count, which is why this features so prominently on Runtime’s Glance. Runtime’s Stopwatch Widget remains one of the few Today Extensions that I use on a regular basis, and I expect I’ll frequently use the Runtime Glance as well.

Runtime's Apple Watch Glance

Runtime's Apple Watch Glance

While a run is in progress the Runtime Glance also shows all the relevant stats for your current run, including your location, time, pace, and distance. If a run isn’t in progress, you can always tap on the glance to start a new one!

I’m really excited to ship this update to Runtime with support for Apple Watch. The update also includes a few bug fixes, as well as the new accompanying features on the iPhone’s stopwatch screen. I was initially a bit skeptical of WatchKit when it was announced in November, and worried about building a watch app that required the presence of an iPhone. But now that the app is finished, I’m actually very happy with what I was able to do with it and really looking forward to getting my watch so that I can use this on all my runs going forward.