Last year, Apple announced SiriKit, which provides a lot of opportunities for the developers to provide functionality to the users via Siri. However, there are restrictions in the domains that can utilize this feature and if your app isn’t part of those domains, then you can’t make a big use of it. This year, Apple announced more domains, which means new opportunities for developers. There are updates in the Payments domain, meaning users can now send/transfer money between their accounts, pay bills and search through their accounts. New domain called visual codes is also introduced, which gives your app a chance to show meaningful visual code in the Siri context. This can be very handy in cases where your app stores digital tickets (for transport, cinemas, sport events etc), and when the user is nearby a place for validating the ticket, they can just say ‘Hey Siri, show my MovieTicketsApp ticket’ and Siri will ask your app to provide the ticket. Another new domain is Lists and Notes, which can be used for adding/removing items to a todo list, or adding notes. It’s really handy domain, which we will explore in more details in this post. We will create an app that will add/remove items to a grocery list.
In order to get started, you will need to grab an Xcode 9 (beta version if you are reading this before September) from Apple’s developer website. The UI of the main app will be really simple, with two screens. The first screen will show the lists that the user has created and when a list is selected, it will open a new screen with the tasks that it contains. Both screens will contain button for adding items (lists or tasks) to the table view:
The data in the main app will be synchronized with the Siri extension – whenever the user does an update (add/remove list/task) from Siri, the change should be reflected in the main app. So let’s first create the extension, you will need to create a new target called Intents extension:
Next, we will need to update the Info.plist of the Siri extension, so it knows which intents we are going to support in our app. Our app will support creation of lists, adding tasks to a list, as well as marking tasks as completed. In order to have this, we will need to add the INCreateTaskListIntent, INAddTasksIntent and INSetTaskAttributeIntent intents to our Info.plist file:
The next challenge we need to tackle is where to store the data the user created, so it’s accessible from both the main app and the Siri extension. Since we are storing the data only locally (we don’t have a backend), shared user defaults is a good option. In order to support this, you will need to create an app group, which then needs to be added to both targets. But in order to configure an app group, you will first need to create an app id from Apple’s developer portal:
Next, you will need to enable App Groups and Siri for the created app id:
Then, under identifiers, go to App Groups and create a new group id with a unique identifier and add this new group id to the app id (I know, a lot of ids :)):
If everything is setup correctly, you should see all the ticks under Capabilities -> App Groups, for both targets:
Now, we can load user defaults with the group id as a suite name and all the data between the app and the extension will be synced. We will create a new class, called ListsManager, which will handle all the changes in the user’s data – the creation and modification of the lists and tasks:
The functionality of the main app is just implementing the table view datasource and delegate methods and using the methods defined in the ListsManager, therefore it’s not that interesting. If you want to see the details, check the source code with the link provided at the end of this post.
We are more interested in the IntentHandler implementation in the SiriKit extension. As mentioned above, we will need to implement protocols from three intents INCreateTaskListIntent, INAddTasksIntent and INSetTaskAttributeIntent.
The first protocol we need to implement is for handling the creation of lists. As we know already, the steps required to implement SiriKit intent are confirm, resolve and handle (the first two being optional). For our use case, we don’t need them, so we will implement only the handle method. The handle method provides the intent, represented by the INCreateTaskListIntent object. This intent contains information about the title of the list that needs to be created, as well as any tasks that the user may have also provided in the request. We should handle this request by providing INCreateTaskListIntentResponse in the completion handler of the method. The INCreateTaskListIntentResponse also contains the information about the title and the tasks, as well some additional things like groupName, identifier and created/modifiedDateComponents, which are not needed in our case. In the implementation provided below, we are first taking the title of the intent and create the list using our ListsManager. Next, we check if there are any tasks that need to be added to the list. If there are, we are converting the strings provided by the intent to INTask objects and we are saving the strings to our ListsManager. If everything goes ok, we are sending a successful response to the completion handler:
Let’s test this. One cool addition to Xcode 9 is that you can test Siri with the simulator. Thanks Apple, no more voice straining and disturbing everyone around you. You can provide a sample text in the Siri scheme and that one will be executed when you run the extension. Click on the schemes button to the right of the Stop icon, then select Manage schemes and edit the Siri extension scheme. You will notice that in the Run action, there’s a new input field ‘Siri Intent Query’, which is the place where you should provide the text:
After you run the Siri extension, you should see a confirmation from Siri that the list has been created:
Also, open the main app and verify that it also created the grocery list there. And that’s all there is to creating lists with SiriKit. Pretty simple!
Next, let’s add some tasks to the list. In order to accomplish this, we need to implement the handle method of the INAddTasksIntentHandling protocol. Here we receive an INAddTasksIntent object, which similar to INCreateTaskListIntent, contains information about the task titles that need to be added, as well as the target list that will be modified. Apart from that, you can also provide spatial, which will display a reminder when the user approaches or leaves a specific location (or time, with a temporal event trigger). Similar to the creation of the list, we are first extracting the title of the list and then we are converting the task titles to INTask objects. We are adding the tasks to the ListsManager and then provide a successful response if everything went fine during the process:
If you run the extension with text like ‘Add milk, sugar and tomato in my grocery list in ListsSiriKit’, you will get the following Siri reply:
The last protocol we need to implement is INSetTaskAttributeIntentHandling from INSetTaskAttributeIntent. This protocol is used when we want to update the state of a task. A task in SiriKit can be in three states: unknown, notCompleted and completed. We want to show the task in the list when it’s in one of the first two states, but when the task is completed, we want to remove it from the list. To do this, we are extracting the title and status from the INSetTaskAttributeIntent. If the status is completed, we are just using the finish(task: title) method from our ListsManager, which goes through the saved tasks and deletes the completed task:
If you tell Siri: ‘Mark Sugar as completed in my grocery list in ListsSiriKit’, it will give you something like this:
And if you open the main app, you will notice that the ‘Sugar’ task is deleted from the grocery list, which is what we’ve expected to happen:
That wraps up this post. The new Lists API opens up a lot of possibilites with different kinds of lists and notes, so you can get pretty creative here. Have fun and also don’t forget to check the complete source code here.