The WW iOS app is a massive project that encompasses a number of different features for our members. In order to help us organize our codebase into logical components, we operate with a multi-pod setup that is relatively popular in the development community. While this has helped us with code organization, it also introduced some interesting challenges along the way. When we started building our Siri Shortcuts implementation last year, we ran into some major architectural problems that every multi-pod project experiences at some point. We created a solution that’s both simple and scalable for the future.
Basic Siri Shortcuts Architecture
Siri Shortcuts acts as an extension to your iOS app, much like an Apple Watch app (at least pre-watchOS 6). This means that the files for it reside in the base Xcode project with your app’s primary target. The actual extension contains a number of standard files, but one of the most important is Intents.intentdefinition, which is where you define your shortcuts and configure some basic behaviors. By default, the shortcut intents you define here are available in your primary app as well, allowing you to donate them and provide a UI for users to setup a shortcut. Integration between your main app and your extension is relatively seamless and any new shortcuts you define will automatically be available in your main app target.
But That Seems Too Easy!
Well, that is too easy, at least for our project setup. In a multi-pod project, your dependency pods are unable to access any classes or objects defined in your main app. That also means your dependencies have no notion of the shortcuts you defined. Depending on how your app is architected, this might not actually be an issue for you. But if your dependencies need to access your shortcuts in any way, such as for donation or setup, you’ll have to do some extra work to get them to play nicely with each other.
Problem, Meet Solution
In order to handle this problem in an elegant way, we decided to use the good old method of dependency inversion. We defined three simple types in our foundational pod that all other pods depend on: ShortcutType, IntentDelegate, and IntentManager (these implementations are shown below).
Let’s break this down a bit. IntentDelegate is a simple protocol that defines methods to perform all of the actions surrounding Siri Shortcuts that our dependencies might have to interact with. IntentManager is a caseless enum that contains an IntentDelegate, which is the object that our dependencies access. In our main project, we create a type (not shown here) that conforms to IntentDelegate. This type is then set as IntentManager’s IntentDelegate at some point during our app’s initialization sequence. This makes it so that if we want to donate an intent in one of our dependencies, we simply do the following:
Why iOS 13.1?
You might be wondering why everything is marked as only available for iOS 13.1+. After all, Siri Shortcuts was introduced with iOS 12. The reasoning here is because there were massive upgrades to how Siri Shortcuts function in iOS 13 to support parameters. While this code can be modified to provide backwards compatibility with iOS 12, it was excluded here for simplicity’s sake.
As far as why the minimum target is iOS 13.1, Apple released iOS 13.0 with a number of major new features excluded. One of them includes conversational shortcuts, which is central to how Siri Shortcuts now operate. Running any Siri Shortcut in iOS 13.0 will cause the Shortcuts app to open rather than having Siri respond as normal. Due to the suboptimal experience, we found it better to simply disable this feature for users running iOS 13.0.
At WW we currently support 13 different locales, which means 13 different translations for each user displaying string we create. While Siri Shortcuts fully support localizations, Xcode doesn’t make it easy to supply those translations.
Unlike most other places in apps where you provide your own key to localize, Xcode actually generates the keys for you for all Siri Shortcuts strings. This means that all keys will be seemingly random and look something like 0xCtgS, which provides basically no information about the usage of that string. To make matters worse, any new strings and keys generated after you generate the localizable Intents.strings files will not automatically be added to the localizable list. This means you’re required to go through and manually track down any new key-value pair that isn’t in the localizable file.
Thankfully, Xcode provides a little bit of relief here. If you select your main project file in the Xcode editor, you can select Editor -> Export for Localization. This will create a new folder containing everything you need for localizing your strings for each locale. The main file you should look for is the .xliff file for each locale. This contains every Siri Shortcuts string you need along with its associated key and the original text it corresponds to. That way, all you have to do is update the files with your translations and import them back into Xcode. It’s not the most ideal way to deal with it, but it’s much better than spending hours scavenging for new keys in a plist!
Building Up from Here
If the recent changes between iOS 12 and 13 show us anything, it’s that Siri Shortcuts is an ever-growing and changing feature that developers will need to readily adapt to in the future. While the infrastructure described here might inevitably break with future updates, it is hopefully resilient enough to only require minor changes.
— Ross Freeman, iOS Engineer, WW
Interested in joining the WW team? Check out the careers page to view technology job listings as well as open positions on other teams.