Creating basic configurable Siri Shortcut

Simplest possible tutorial to get you started with Siri Intents.

Published: April 12, 2021
See books

I have recently implemented my first Siri Shortcuts and wanted to share my experience mostly because I found the existing tutorials too complicated. In this one, I want to present the minimal implementation, so you can get it running and then build upon that. Because the fewer steps, the fewer issues can happen.

The are couple of flavors, when it comes to implementing Siri Shortcuts. This post deals with the "Intents" variant which offers more freedom and the shortcut is configurable in the Shortcuts app.

I should also add that I am far from expert in this topic.

Shoutout to Drew Westcott for checking out the draft.

Let's get started.

Add the capability

This first step is pretty straightforward. You need to open your project settings, select the "Signing & Capabilities" tab and add the "Siri" capability.

Siri Shortcuts tutorial - adding capability

Create Intents file

Next, we will create the Intents.intentdefinition file.

Siri Shortcuts tutorial - creating intents file

When you open this in Xcode, you will get an UI based editor to create your intents. There are a lot of things to fill in, but they are pretty self-explanatory. At least in my case, because this intent takes just basic parameters.

The first section "Custom Intent" lets you specify the category, title and description along with some checkboxes.

Siri Shortcuts tutorial - custom intent configuration

Next is the "Parameters" section. These are the parameters which are configurable in the Shortcut app and can be supplied via Siri.

In my case I created the "task" parameter which is of type "String" and I toggled the "Configurable" and "Resolvable" checkboxes. My second parameter is "time" which is of type "Duration". Here I can also set allowed units, which are minutes and hours in my case.

Siri Shortcuts tutorial - parameters configuration

Next there is section "Shortcuts app" where you specify input and key parameters and "Supported Combinations". In my case I always need both, so there is single combination. This has a short summary which is displayed in the Shortcuts app.

Siri Shortcuts tutorial - Shortcuts app configuration

Last section is "Suggestions" which is very similar to the "Shortcuts app" as setting it up goes. Here you can also specify longer description and allow background execution.

Siri Shortcuts tutorial - suggestions configuration

Intent response

After you finish setting up the intent, select the "Response" in the custom intents list. There are already prepared templates for success and failure. So I just added basic information for the user.

Siri Shortcuts tutorial - intent response configuration

Create Intents Extension

Now it's time for the code. In Xcode use the File menu to add new "Target" and select "Intents Extension".

Siri Shortcuts tutorial - intent file creation

This will create IntentHandler file.

Select your Intents.intentdefinition file and add it also to this extension with the "Target Membership" option.

If you open it, there is just a bit of code.

import Intents

class IntentHandler: INExtension {
    override func handler(for intent: INIntent) -> Any {

    }
} 

If you have multiple intents configured, you can check the intent parameter and decide what handler to return.

In my case, I have just a single one. Since my intent is named "CreateReminderIntent", I created the CreateReminderIntentHandler.

This class needs to conform to the CreateReminderIntentHandling, which gets automatically created based on the Intents.intentdefinition file.

Implement custom intent handler

To correctly implement the protocol CreateReminderIntentHandling, we need the method handle along with resolve methods for all the specified parameters in the intents file.

Here is the basic skeleton:

final class CreateReminderIntentHandler: NSObject, CreateReminderIntentHandling {
    func resolveTask(for intent: CreateReminderIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
    }

    func resolveTime(for intent: CreateReminderIntent, with completion: @escaping (INTimeIntervalResolutionResult) -> Void) {
    }

    func handle(intent: CreateReminderIntent, completion: @escaping (CreateReminderIntentResponse) -> Void) {

    }
}

Since I have a task and time parameters, I need to resolve these two. I am not actually sure why this is here. Maybe if I didn't allow Siri to ask for details, I could drop this, but the implementation seems straightforward enough.

The resolving logic depends on your use case. For example my task parameter is resolved like this:

func resolveTask(for intent: CreateReminderIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
    guard let task = intent.task else {
        completion(.confirmationRequired(with: intent.task))
        return
    }

    completion(.success(with: task))
}

If the intent contains value, I return .success, if not, I ask for confirmation.

Let's look at the handle implementation:

func handle(intent: CreateReminderIntent, completion: @escaping (CreateReminderIntentResponse) -> Void) {
    guard let task = intent.task, let duration = intent.time else {
        completion(CreateReminderIntentResponse(code: .failure, userActivity: nil))
        return
    }
    // non-relevant logic omitted 

    switch result {
    case .success:
        completion(CreateReminderIntentResponse(code: .success, userActivity: nil))
    default:
        completion(CreateReminderIntentResponse(code: .failure, userActivity: nil))
    }
}

I am basically just checking that I have the needed parameters, and then I use my code from the main app to create the reminder. The result is also my custom type.

According to docs the userActivity is possibly used when Siri launches the app to finish the task. Since I don't need to launch the app (and indeed don't want to), I am just passing nil.

And now we can update the generated code:

class IntentHandler: INExtension {
    override func handler(for intent: INIntent) -> Any {
        return CreateReminderIntentHandler()
    }
}

Configure Info.plist files

Xcode 13 appears to do this step automatically for both Info.plist files, but I would still at least check it - just to be sure.

This last step is critical, otherwise your intent handler is not going to be called. You need to modify Info.plist in your main app and also in the Intents extension.

Let's start with the main app. I recommend opening the file as source code, and then you need to add this part:

<key>NSUserActivityTypes</key>
<array>
    <string>CreateReminderIntent</string>
</array>

In this array you would add all the intents from Intents.intentdefinition. Notice the name. "CreateReminderIntent". I initially used "CreateIntent" as this is name in the intent definition file and then also tried "CreateReminderIntentHandler". Both of these did not work. Shortcuts app would just launch my main app when testing the shortcut.

So just to reiterate. If you have intent named "DoCoolStuff", in the Info.plist you need to put "DoCoolStuffIntent".

And now open the Info.plist in the extension. Under the key "NSExtension", you should have something like this:

<key>NSExtensionAttributes</key>
    <dict>
        <key>IntentsRestrictedWhileLocked</key>
        <array/>
        <key>IntentsRestrictedWhileProtectedDataUnavailable</key>
        <array/>
        <key>IntentsSupported</key>
        <array>
            <string>CreateReminderIntent</string>
        </array>
    </dict>

Since my intents are quite simple, I don't need to worry about phone being locked or protected data not being available. If this were the case for you, you would need to put intent name also under these keys.

Done!

If you followed all the steps, you should have working Siri Shortcut (Intent). All that is left to do is to test it via Siri or Shortcuts app.

Here is the checklist:

  • Capability
  • Intents.intentdefinition file
  • Intents Extension
  • Custom intent handler
  • Modified Info.plist files

Uses: Xcode 12 & Swift 5.3

Bluesky logo

Follow on Bluesky to not miss new posts

Filip Němeček profile photo

WRITTEN BY

Filip Němeček Mastodon

iOS blogger and developer with interest in Python/Django.

iOS blogger and developer with interest in Python/Django.