Donating shortcuts with INinteractions

Donating shortcuts with user activities is pretty straightforward and powerful. The only downside is that your app has to be launched to handle shortcuts when the user executes them. Sometimes this might be the behavior you need, but for the case of booking an ap

To achieve this, you need to implement a custom intent, and an extension that can handle it. Let's set up the custom intent and prepare the app for donating and potentially handling the intent. After doing this, you will see how to implement an Intents extension that handles your custom intent.

The first step to implement your own custom intent is to create an intent definition file. Add a new file to your project and choose the SiriKit Intent Definition File type from the list of available files, as shown in the following screenshot:

When you select this file in Xcode, the intent editor is opened. In this editor, you can create your custom intents and configure them. Click the + icon in the bottom left corner of the editor to create a new intent. Name your new intent BookAppointment.

After creating your intent, you need to choose a category for your intent. There are many available categories to choose from. You should always pick the category that fits your needs as closely as possible. In this case, the Book category is a perfect choice. The next step is to give your intent a title and a description, as shown in the following screenshot:

The parameters for your intent are configured in the Parameters section. The appointment booking intent should have two parameters:

  • hairdresser (String)
  • day (String)

These parameters should appear in Xcode as follows:

Xcode will generate several subclasses for your custom intent after you have configured it. The parameters you specify in the Parameters section will be available as properties on your custom intent.

Now that you have the parameters prepared, you need to configure the shortcut that you want to donate. Every shortcut you base on the intent you just created will have a unique combination of parameters. These parameters can be used to generate a dynamic title for your shortcut.

Add a parameter combination for the hairdresser and day parameters, and create a title that uses these parameters. You can start typing the message, and Xcode will automatically suggest the parameters for you. The final shortcut should look like the following screenshot:

After configuring the intent and shortcut, you can set up response parameters. You can use these parameters to create a helpful response that confirms information for the user. In this case, you can add a hairdresser property and use that to verify a user's appointment, as shown in the following screenshot:

Now that the custom intent is set up, you can update the AppointmentShortcutHelper to donate an INInteraction, which contains the custom intent, to Siri. Add the following implementation for this helper to AppointmentShortcutHelper:

static func donateInteractionForAppointment(_ appointment: Appointment) {
  let intent = BookAppointmentIntent()
  intent.hairdresser = appointment.hairdresser
  intent.day = appointment.day
  let title = "Book an appointment with \(appointment.hairdresser!) for \(appointment.day!)"
  intent.suggestedInvocationPhrase = title

  let interaction = INInteraction(intent: intent, response: nil)
  interaction.donate(completion: nil)
}

Xcode has generated a BookAppointmentIntent class based on your custom intent definition. As mentioned before, this intent has the properties that you have defined in the editor, so that you can properly configure your intent.

After configuring the intent, it is wrapped in an INInteraction object. To donate the shortcut to Siri, you must call donate(completion:) on the interaction object. Next, replace the user activity-based donation code in AddAppointmentViewController with the following line:

AppointmentShortcutHelper.donateInteractionForAppointment(appointment)`

If your application does happen to be launched by Siri to handle the intent, you should do so in the application(_:continue:restorationHandler:) method in AppDelegate. The user activity type for your custom intent will be the name of the intent you created. So, if you created an intent called BookAppointment, that will be the user activity type that Siri will use. Update the implementation of application(_:continue:restorationHandler:) as follows:

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

  var allowedActivities = [NSUserActivity.identifierForAppointment, "BookAppointment"]

  guard allowedActivities.contains(userActivity.activityType)
    else { return false }

  AppointmentShortcutHelper.storeAppointmentForActivity(userActivity)
  return true
}

This wraps up the configuration of your app. The next step is to create an Intents Extension that can handle the new custom intent. Since you have already done this before, the processes won't be explained step by step. Instead, make sure you do all of the following:

  1. Create an Intents Extension.
  2. Enable App Groups for the extension.
  3. Add the intent definition, managed object context extension, data helpers, and Core Data model to the new extension's target.
  4. Configure the extension to support the BookAppointment intent.
  5. Remove the boilerplate code from the IntentHandler class.

Since the IntentHandler for the custom intent is only meant to handle a single intent, add the following implementation for the IntentHandler class:

class IntentHandler: INExtension, BookAppointmentIntentHandling {

  override func handler(for intent: INIntent) -> Any {
    return self
  }

  func handle(intent: BookAppointmentIntent, completion: @escaping (BookAppointmentIntentResponse) -> Void) {
    guard let hairdresser = intent.hairdresser, let day = intent.day else {
      completion(BookAppointmentIntentResponse(code: .failure, userActivity: nil))
      return
    }

    let moc = PersistentHelper.shared.persistentContainer.viewContext

    moc.persist {
      let appointment = Appointment(context: moc)
      appointment.day = day
      appointment.hairdresser = hairdresser

      let response = BookAppointmentIntentResponse(code: .success, userActivity: nil)
      response.hairdresser = hairdresser
      completion(response)
    }
  }
}

If you book a new appointment in the Hairdressers app and invoke the Siri Shortcut later, the Intents extension will be used to handle your intent rather than having to open the app. This is great, because it allows the user to go on with what they were doing, without having to look at your app for confirmation.

Now that your extension is in place, go ahead and play with your Siri Shortcuts a bit. The work for this chapter is completed—you have created an application that is tightly integrated with Siri in order to streamline the user's experience with your app.