Introduction
Since Siri was introduced back in 2011, iOS developers have been asking for the possibility to integrate third-party apps with it. With the release of iOS 10 during WWDC 2016, Apple finally made SiriKit available to developers. There are still some restrictions on which types of applications can take advantage of Siri, but it's a step in the right direction. Let's take a look at what we can do with Siri.
For more on SiriKit and the other new features for developers in iOS 10, check out Markus Mühlberger's course, right here on Envato Tuts+.
Supported Domains
To make use of SiriKit, your app has to be in one or more of the following domains:
- VoIP calling (for example, Skype)
- Messaging (WhatsApp)
- Payments (Square, PayPal)
- Photo (Photos)
- Workouts (Runtastic)
- Ride booking (Uber, Lyft)
- CarPlay (automotive vendors only)
- Restaurant reservations (requires additional support from Apple)
If your app doesn't belong to any of these categories, unfortunately you cannot use Siri in your app at this moment. Don't leave just yet, though, because SiriKit is very powerful, and it may gain new capabilities in the future!
Extension Architecture
A SiriKit extension is in reality composed of two types of extension. An Intents
extension is required and takes care of handling the requests by the user and executing a specific task in your app (such as starting a call, sending a message, etc.).
On the other hand, an IntentsUI
extension is not mandatory. You should only create one if you want to customize the user interface that Siri shows when presenting your data. If you don't do this, the standard Siri interface will be displayed. We are going to take a look at both types of extension in this tutorial.
For your information, during WWDC 2016 Apple released two very interesting videos about SiriKit. You may want to check them out:
Example Project
We are going to build a simple app that processes payments via Siri. The goal is to successfully process the sentence "Send $20 to Patrick via TutsplusPayments". The format of the sentence consists of an amount of money with a specific currency, the name of the payee, and the app to use to complete the transaction. We are later going to analyze the payment intent in more detail.
Initial Setup
Let's start by creating a standard Xcode project in Swift and giving it a name. There are a few mandatory steps that you have to do before writing any code to enable your app to use Siri's APIs.
1. Select your Target > Capabilities and enable the Siri capability. Make sure that the entitlements were created successfully in your project structure.
2. Open your app's Info.plist
and add the key NSSiriUsageDescription
. The value must be a string explaining your usage of Siri that will be shown to the user when asked for the initial permission.
3. Select File > New > Target. In the new window presented by Xcode, under Application Extensions, choose Intents Extension. Also select the option to include a UI Extension. This will save you from later having to create another separate extension.
In the Info.plist
file of your newly created Intents
target, fully expand the NSExtension
dictionary to study its contents. The dictionary describes in more detail which intents your extension supports and if you want to allow the user to invoke an intent while the device is locked.
Insert the most relevant intents at the top if you want to support more than one. Siri uses this order to figure out which one the user wants to use in case of ambiguity.
We now need to define which intents we want to support. In this example, we are going to build an extension that supports the payment intent. Modify the Info.plist
file to match the following picture.
Here we specify that we want to handle the INSendPaymentIntent
and that we require the device to be unlocked. We don't want strangers to send payments when the device is lost or stolen!
iOS Target
The next step actually involves writing some code in the iOS app. We have to ask the user for permission to send their voice to Apple for analysis. We simply have to import the Intents
framework and call the appropriate method as follows:
import UIKit import Intents class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Ask permission to access Siri INPreferences.requestSiriAuthorization { authorizationStatus in switch authorizationStatus { case .authorized: print("Authorized") default: print("Not Authorized") } } } }
The resulting dialog presented to the user during the first launch of the app will look like this.
This is all we have to do in our simple iOS app. Let's get into the extensions world now!
Intents Extension
Switch to the Intents
extension that we created earlier. Expand its contents in the Xcode project navigator. You'll see just one file named IntentHandler.swift
.
This file is the entry point of your extension and is used to handle any intents that Siri sends you. Siri will forward to the handler(for:)
method all the intents in case your extension supports multiple types. It's your job to check the type of the INIntent
object and handle it appropriately.
The IntentHandler.swift
template already contains an example implementation of a Messaging intent. Replace all the code with the following empty method so that we can walk together through each step.
class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any? { // This is the default implementation. If you want different objects to handle different intents, // you can override this and return the handler you want for that particular intent. return self } }
Each intent has an associated protocol to make sure a class implements all the required methods. Most of the protocols in the Intents framework have the same structure.
The protocol that we are going to implement is called INSendPaymentIntentHandling
. This protocol contains the following required and optional methods:
-
Required:
handle(sendPayment:completion:)
-
Optional:
confirm(sendPayment:completion:)
resolvePayee(forSendPayment:with:)
resolveCurrencyAmount(forSendPayment:with:)
resolveNote(forSendPayment:with:)
Let's create an extension of the IntentHandler
class in the same Swift file to implement the only required method.
extension IntentHandler: INSendPaymentIntentHandling { func handle(sendPayment intent: INSendPaymentIntent, completion: @escaping (INSendPaymentIntentResponse) -> Void) { // Check that we have valid values for payee and currencyAmount guard let payee = intent.payee, let amount = intent.currencyAmount else { return completion(INSendPaymentIntentResponse(code: .unspecified, userActivity: nil)) } // Make your payment! print("Sending \(amount) payment to \(payee)!") completion(INSendPaymentIntentResponse(code: .success, userActivity: nil)) } }
This is a very basic implementation. We make sure there is a valid payee
and currencyAmount
to set the transaction as successful. You may not believe it, but it works already! Select the Intents scheme from Xcode and run it. When Xcode presents the usual menu to choose an app to run, select Siri.
When Siri starts, try to say, "Send $20 to Patrick via TutsplusPayments". Now enjoy your first successful payment completed with your voice!
You can also try to test the failing case. Try to say the same sentence as before but without specifying the payee (i.e: "Send $20 via TutsplusPayments"). You'll see that Siri will fail and present the user a button to continue the payment in your app.
In case Siri does not understand or is not provided with one of the optional parameters but you require a valid value, you can implement one of the resolve methods. Those methods present the user an option to give more details about the payment such as the name of payee, the exact currency amount, and even a note. With this smart architecture of the API, you as developer are presented with the possibility to easily and clearly understand your user's request in different ways.
In a real-world application, you would create a dynamic framework that is shared between your iOS app and extensions. By making use of this architecture, you can share the same business logic in multiple targets. You won't need to implement it multiple times, but just once and for all targets!
Intents UI Extension
In the last part of this tutorial, I am going to show you how you can customize the user interface that Siri displays.
First of all, remember to set the correct intent class that you want to handle in the Info.plist
of the ExtensionUI
, as we did in the previous section.
Jump into the Intents UI extension and you'll see the template that Xcode has created for you. It contains an IntentViewController
, which is a simple subclass of UIViewController
that implements the INUIHostedViewControlling
protocol. A Storyboard file was also created for you; open it so that we can start customizing the user interface.
Add a UIImageView
as the background and a label in the center. Download the background image, import it into the Intents UI target, and set it as the image of the newly created UIImageView
. Create a UILabel
object and position it at the center of the view. You can easily use AutoLayout
to set up the constraints in Storyboard.
Open the Assistant Editor and create an @IBOutlet
for your label and call it contentLabel
. The result should look like something like this:
Open the IntentViewController
file and you'll see a bunch of example code. You can remove everything except the configure(with:context:completion:)
method that we are going to implement now. This method is called when the user interface is ready to be configured. What we have to do here is set the content of the UILabel
.
class IntentViewController: UIViewController, INUIHostedViewControlling { @IBOutlet weak var contentLabel: UILabel! // MARK: - INUIHostedViewControlling func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) { if let paymentIntent = interaction.intent as? INSendPaymentIntent { // If any of this properties is not set, use the default UI. guard let amount = paymentIntent.currencyAmount?.amount, let currency = paymentIntent.currencyAmount?.currencyCode, let name = paymentIntent.payee?.displayName else { return completion(CGSize.zero) } let paymentDescription = "\(amount)\(currency) to \(name)" contentLabel.text = paymentDescription } if let completion = completion { completion(self.desiredSize) } } var desiredSize: CGSize { return self.extensionContext!.hostedViewMaximumAllowedSize } }
First of all, we check that the intent
object is of type INSendPaymentIntent
. If it is, we also make sure that all the properties that we want to display are not nil
, otherwise we simply call the completion block with a size of zero to hide our custom view. If everything goes as we expect, we create a custom string with the data that we want to show to the user and set it as the text of the contentLabel
.
Run the extension again and you'll see the new view inside Siri!
Siri still shows the default view. We can hide it by making our view controller conform to the INUIHostedViewSiriProviding
protocol.
class IntentViewController: UIViewController, INUIHostedViewControlling, INUIHostedViewSiriProviding { // Previous code goes here... var displaysPaymentTransaction: Bool { return true } }
By returning true
in the displaysPaymentTransaction
variable, we tell Siri that our view controller is taking care of displaying all the necessary information to the user and that the default view can be hidden. The result is much cleaner now!
Note: As you can see in this picture, when specifying a different currency than US dollars, Siri correctly understands and returns the currency code to the extension. Unfortunately, the text always displays US dollars. I have reported this bug to Apple!
Conclusion
I hope you have enjoyed this tutorial. Siri is very powerful even if limited to some types of applications at the moment. If you plan to implement it in your own apps, make sure to market it well to your users because they may not be aware of how cool and advanced your app has become!
If you want to learn more about integrating Siri in your app, or if you want to find out about some of the other cool developer features of iOS 10, check out Markus Mühlberger's course.
Also, check out some of our other free tutorials on iOS 10 features.
Comments