Implementing SiriKit in Xamarin.iOS
This article covers the steps required to implement SiriKit support in a Xamarin.iOS apps.
New to iOS 10, SiriKit allows a Xamarin.iOS app to provide services that are accessible to the user using Siri and the Maps app on an iOS device. This article covers the steps required to implement SiriKit support in the Xamarin.iOS apps by adding the required Intents Extensions, Intents UI Extensions and Vocabulary.
Siri works with the concept of Domains, groups of know actions for related tasks. Each interaction that the app has with Siri must fall into one of its known service Domains as follows:
- Audio or video calling.
- Booking a ride.
- Managing workouts.
- Messaging.
- Searching photos.
- Sending or receiving payments.
When the user makes a request of Siri involving one of the App Extension's services, SiriKit sends the extension an Intent object that describes the user's request along with any supporting data. the App Extension then generates the appropriate Response object for the given Intent, detailing how the extension can handle the request.
This guide will present a quick example of including SiriKit support into an existing app. For the sake of this example, we'll be using the fake MonkeyChat app:
MonkeyChat keeps its own contact book of the user's friends, each associated with a screen name (like Bobo for example), and allows the user to send text chats to each friend by their screen name.
Extending the App with SiriKit
As shown in the Understanding SiriKit Concepts guide, there are three main parts involved in extending an app with SiriKit:
These include:
- Intents Extension - Verifies the users responses, confirms the app can handle the request and actually performs the task to fulfill the user's request.
- Intents UI Extension - Optional, provides a custom UI to the responses in the Siri Environment and can bring the apps UI and branding into Siri to enrich the user's experience.
- App - Provides the App with User specific vocabularies to assist Siri in working with it.
All of these elements and the steps to include them in the app will be covered in detail in the sections below.
Preparing the App
SiriKit is built on Extensions, however, before adding any Extensions to the app, there are a few things that the developer needs to do to help with the adoption of SiriKit.
Moving Common Shared Code
First, the developer can move some of the common code that will be shared between the app and the extensions into either Shared Projects, Portable Class Libraries (PCLs) or Native Libraries.
The Extensions will need to be able to do all of the things that the app does. In the terms of the sample MonkeyChat app, things like finding contacts, adding new contacts, send messages and retrieve message history.
By moving this common code into a Shared Project, PCL or Native Library it makes it easy to maintain that code in one common place and ensures that the Extension and the parent app provide uniform experiences and functionality for the user.
In the case of the example app MonkeyChat, the data models and processing code such as network and database access will be moved into a Native Library.
Do the following:
Start Visual Studio for Mac and open the MonkeyChat app.
Right-click on the Solution Name in the Solution Pad and select Add > New Project...:
Select iOS > Library > Class Library and click the Next button:
Enter
MonkeyChatCommon
for the Name and click the Create button:Right-click on the References folder of the main app in the Solution Explorer and select Edit References.... Check the MonkeyChatCommon project and click the OK button:
In the Solution Explorer, drag the common shared code from the main app to the Native Library.
In the case of MonkeyChat, drag the DataModels and Processors folders from the main app into the Native Library:
Edit any of the files that were moved to the Native Library and change the namespace to match that of the library. For example, changing MonkeyChat
to MonkeyChatCommon
:
using System;
namespace MonkeyChatCommon
{
/// <summary>
/// A message sent from one user to another within a conversation.
/// </summary>
public class MonkeyMessage
{
public MonkeyMessage ()
{
}
...
}
}
Next, go back to the main app and add a using
statement for the Native Library's namespace anywhere the app uses one of the classes that have been moved:
using System;
using System.Collections.Generic;
using UIKit;
using Foundation;
using CoreGraphics;
using MonkeyChatCommon;
namespace MonkeyChat
{
public partial class MasterViewController : UITableViewController
{
public DetailViewController DetailViewController { get; set; }
DataSource dataSource;
...
}
}
Architecting the App for Extensions
Typically, an app will sign up for multiple Intents and the developer needs to ensure that the app is architected for the appropriate number of Intent Extensions.
In the situation where an app requires more than one Intent, the developer has the option of placing all of its Intent handling in one Intent Extension or creating a separate Intent Extension for each Intent.
If choosing to create a separate Intent Extension for each Intent, the developer could end up duplicating a large amount of boilerplate code in each extension and create a large amount of processor and memory overhead.
To help choose between the two options, see if any of the Intents naturally belong together. For example, an app that made audio and video calls might want to include both of those Intents in a single Intent Extension as they are handling similar tasks and could provide the most code reuse.
For any Intent or group of Intents that don't fit into an existing group, create a new Intent Extension in the app's solution to contain them.
Setting the Required Entitlements
Any Xamarin.iOS app that includes SiriKit integration, must have the correct entitlements set. If the developer doesn't set these required entitlements correctly, they will not be able to install or test the app on real iOS 10 (or greater) hardware, which is also requirement since the iOS 10 Simulator doesn't support SiriKit.
Do the following:
Double-click the
Entitlements.plist
file in the Solution Explorer to open it for editing.Switch to the Source tab.
Add the
com.apple.developer.siri
Property, set the Type toBoolean
and the Value toYes
:Save the changes to the file.
Double-click the Project File in the Solution Explorer to open it for editing.
Select iOS Bundle Signing and ensure that the
Entitlements.plist
file is selected in the Custom Entitlements field:Click the OK button to save the changes.
When finished, the app's Entitlements.plist
file should look like the following (in opened in an external editor):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.siri</key>
<true/>
</dict>
</plist>
Correctly Provisioning the App
Because of the strict security that Apple has placed around the SiriKit framework, any Xamarin.iOS app that implements SiriKit must have the correct App ID and Entitlements (see section above) and must be signed with a proper Provisioning Profile.
Do the following on your Mac:
In a web browser, navigate to https://developer.apple.com and log into your account.
Click on Certificates, Identifiers and Profiles.
Select Provisioning Profiles and select App IDs, then click the + button.
Enter a Name for the new Profile.
Enter a Bundle ID following Apple’s naming recommendation.
Scroll down to the App Services section, select SiriKit and click the Continue button:
Verify all of the settings, then Submit the App ID.
Select Provisioning Profiles > Development, click the + button, select the Apple ID, then click Continue.
Click Select All, then click Continue.
Click Select All again, then click Continue.
Enter a Profile Name using Apple’s naming suggestions, then click Continue.
Start Xcode.
From the Xcode menu select Preferences…
Select Accounts, then click the View Details… button:
Click the Download All Profiles Button in the lower left hand corner:
Ensure that the Provisioning Profile created above has been installed in Xcode.
Open the project to add SiriKit support to in Visual Studio for Mac.
Double-click the
Info.plist
file in the Solution Explorer.Ensure that the Bundle Identifier matches the one created in Apple's Developer Portal above:
In the Solution Explorer, select the Project.
Right-click the project and select Options.
Select iOS Bundle Signing, select the Signing Identity and Provisioning Profile created above:
Click the OK button to save the changes.
Important
Testing SiriKit only works on a real iOS 10 Hardware Device and not in the iOS 10 Simulator. If having issues installing a SiriKit enabled Xamarin.iOS app on real hardware, ensure that the required Entitlements, App ID, Signing Identifier and Provisioning Profile have been correctly configured in both Apple's Developer Portal and Visual Studio for Mac.
Requesting Siri Authorization
Before the app adds any User Specific Vocabulary or the Intents Extensions connects to Siri, it must requests authorization from the user to access Siri.
Edit the app's Info.plist
file, switch to the Source view and add the NSSiriUsageDescription
key with a string value describing how the app will use Siri and what types of data will be sent. For example, the MonkeyChat app might say "MonkeyChat contacts will be sent to Siri":
Call the RequestSiriAuthorization
method of the INPreferences
class when the app first starts. Edit the AppDelegate.cs
class and make the FinishedLaunching
method look like the following:
using Intents;
...
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
// Request access to Siri
INPreferences.RequestSiriAuthorization ((INSiriAuthorizationStatus status) => {
// Respond to returned status
switch (status) {
case INSiriAuthorizationStatus.Authorized:
break;
case INSiriAuthorizationStatus.Denied:
break;
case INSiriAuthorizationStatus.NotDetermined:
break;
case INSiriAuthorizationStatus.Restricted:
break;
}
});
return true;
}
The first time this method is called, an alert is displayed prompting the user to allow the app to access Siri. The message that the developer added to the NSSiriUsageDescription
above will be displayed in this alert. If the user initially denies access, they can use the Settings app to grant access to the app.
At any time, the app can check the app's ability to access Siri by calling the SiriAuthorizationStatus
method of the INPreferences
class.
Localization and Siri
On an iOS device, the user is able to select a language for Siri that is different then the system default. When working with localized data, the app will need to use the SiriLanguageCode
method of the INPreferences
class to get the language code from Siri. For example:
var language = INPreferences.SiriLanguageCode();
// Take action based on language
if (language == "en-US") {
// Do something...
}
Adding User Specific Vocabulary
The User Specific Vocabulary is going to provide words or phrases that are unique to individual users of the app. These will be provided at runtime from the main app (not the App Extensions) as an ordered set of terms, ordered in a most significant usage priority for the users, with the most important terms at the start of the list.
User Specific Vocabulary must belong to one of the following categories:
- Contact Names (that are not managed by the Contacts framework).
- Photo Tags.
- Photo Album Names.
- Workout Names.
When selecting terminology to register as custom vocabulary, only choose terms that might be misunderstood by someone not familiar with the app. Never register common terms such as "My Workout" or "My Album". For example, the MonkeyChat app will register the nicknames associated with each contact in the user's address book.
The app provides the User Specific Vocabulary by calling the SetVocabularyStrings
method of the INVocabulary
class and passing in a NSOrderedSet
collection from the main app. The app should always call the RemoveAllVocabularyStrings
method first, to remove any existing terms before adding new ones. For example:
using System;
using System.Linq;
using System.Collections.Generic;
using Foundation;
using Intents;
namespace MonkeyChatCommon
{
public class MonkeyAddressBook : NSObject
{
#region Computed Properties
public List<MonkeyContact> Contacts { get; set; } = new List<MonkeyContact> ();
#endregion
#region Constructors
public MonkeyAddressBook ()
{
}
#endregion
#region Public Methods
public NSOrderedSet<NSString> ContactNicknames ()
{
var nicknames = new NSMutableOrderedSet<NSString> ();
// Sort contacts by the last time used
var query = Contacts.OrderBy (contact => contact.LastCalledOn);
// Assemble ordered list of nicknames by most used to least
foreach (MonkeyContact contact in query) {
nicknames.Add (new NSString (contact.ScreenName));
}
// Return names
return new NSOrderedSet<NSString> (nicknames.AsSet ());
}
// This method MUST only be called on a background thread!
public void UpdateUserSpecificVocabulary ()
{
// Clear any existing vocabulary
INVocabulary.SharedVocabulary.RemoveAllVocabularyStrings ();
// Register new vocabulary
INVocabulary.SharedVocabulary.SetVocabularyStrings (ContactNicknames (), INVocabularyStringType.ContactName);
}
#endregion
}
}
With this code in place, it might be called as follows:
using System;
using System.Threading;
using UIKit;
using MonkeyChatCommon;
using Intents;
namespace MonkeyChat
{
public partial class ViewController : UIViewController
{
#region AppDelegate Access
public AppDelegate ThisApp {
get { return (AppDelegate)UIApplication.SharedApplication.Delegate; }
}
#endregion
#region Constructors
protected ViewController (IntPtr handle) : base (handle)
{
// Note: this .ctor should not contain any initialization logic.
}
#endregion
#region Override Methods
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Do we have access to Siri?
if (INPreferences.SiriAuthorizationStatus == INSiriAuthorizationStatus.Authorized) {
// Yes, update Siri's vocabulary
new Thread (() => {
Thread.CurrentThread.IsBackground = true;
ThisApp.AddressBook.UpdateUserSpecificVocabulary ();
}).Start ();
}
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
#endregion
}
}
Important
Siri treats custom vocabulary as hints and will incorporate as much of the terminology as possible. However, space for custom vocabulary is limited making it important to register only the terminology that might be confusing, therefore keeping the total number of registered terms to a minimum.
For more information, please see our User Specific Vocabulary Reference and Apple's Specifying Custom Vocabulary Reference.
Adding App Specific Vocabulary
The App Specific Vocabulary defines the specific words and phrases that will be known to all of the app's users, such as vehicle types or workout names. Because these are part of the application, they are defined in a AppIntentVocabulary.plist
file as part of the main app bundle. Additionally, these words and phrases should be localized.
App Specific Vocabulary terms must belong to one of the following categories:
- Ride Options.
- Workout Names.
The App Specific Vocabulary file contains two root level keys:
ParameterVocabularies
Required - Defines the app's custom terms and Intent Parameters they apply to.IntentPhrases
Optional - Contains example phrases using the custom terms defined in theParameterVocabularies
.
Each entry in the ParameterVocabularies
must specify an ID string, term and the Intent that the term applies to. Additionally, a single term might apply to multiple Intents.
For a full list of acceptable values and required file structure, please see Apple's App Vocabulary File Format Reference.
To add a AppIntentVocabulary.plist
file to the app project, do the following:
Right-click the Project Name in the Solution Explorer and select Add > New File... > iOS:
Double-click the
AppIntentVocabulary.plist
file in the Solution Explorer to open it for editing.Click the + to add a key, set the Name to
ParameterVocabularies
and the Type toArray
:Expand
ParameterVocabularies
and click the + button and set the Type toDictionary
:Click the + to add a new key, set the Name to
ParameterNames
and the Type toArray
:Click the + to add a new key with the Type of
String
and the value as one of the available Parameter Names. For example,INStartWorkoutIntent.workoutName
:Add the
ParameterVocabulary
key to theParameterVocabularies
key with the Type ofArray
:Add a new key with the Type of
Dictionary
:Add the
VocabularyItemIdentifier
key with the Type ofString
and specify a unique ID for the term:Add the
VocabularyItemSynonyms
key with the Type ofArray
:Add a new key with the Type of
Dictionary
:Add the
VocabularyItemPhrase
key with the Type ofString
and the term the app are defining:Add the
VocabularyItemPronunciation
key with the Type ofString
and the phonetic pronunciation of the term:Add the
VocabularyItemExamples
key with the Type ofArray
:Add a few
String
keys with example uses of the term:Repeat the above steps for any other custom terms the app need to define.
Collapse the
ParameterVocabularies
key.Add the
IntentPhrases
key with the Type ofArray
:Add a new key with the Type of
Dictionary
:Add the
IntentName
key with the Type ofString
and Intent for the example:Add the
IntentExamples
key with the Type ofArray
:Add a few
String
keys with example uses of the term:Repeat the above steps for any Intents the app need to provide example usage of.
Important
The AppIntentVocabulary.plist
will be registered with Siri on the test devices during development and it might take some time for Siri to incorporate the custom vocabulary. As a result, the tester will need to wait several minutes before attempting to test App Specific Vocabulary when it has been updated.
For more information, please see our App Specific Vocabulary Reference and Apple's Specifying Custom Vocabulary Reference.
Adding an Intents Extension
Now that the app has been prepared to adopt SiriKit, the developer will need to add one (or more) Intents Extensions to the solution to handle the Intents required for Siri integration.
For each Intents Extension required, do the following:
- Add an Intents Extension project to the Xamarin.iOS app solution.
- Configure the Intents Extension
Info.plist
file. - Modify the Intents Extension main class.
For more information, please see our The Intents Extension Reference and Apple's Creating the Intents Extension reference.
Creating the Extension
To add an Intents Extension to the solution, do the following:
Right-click on the Solution Name in the Solution Pad and select Add > Add New Project....
From the dialog box select iOS > Extensions > Intent Extension and click the Next button:
Next enter a Name for the Intent Extension and click the Next button:
Finally, click the Create button to add the Intent Extension to the apps solution:
In the Solution Explorer, right-click on the References folder of the newly created Intent Extension. Check the name of the common shared code library project (that the app created above) and click the OK button:
Repeat these steps for the number of Intent Extensions (based on Architecting the App for Extensions section above) that the app will require.
Configuring the Info.plist
For each of the Intents Extensions that have added to the app's solution, must be configured in the Info.plist
files to work with the app.
Just like any typical App Extension, the app will have the existing keys of NSExtension
and NSExtensionAttributes
. For an Intents Extension there are two new attributes that must be configured:
- IntentsSupported - Is required and consists of an array of Intent Class names that the app wants to support from the Intent Extension.
- IntentsRestrictedWhileLocked - Is an optional key for the app to specify the extension's lock screen behavior. It consists of an array of Intent Class names that the app wants to require the user to be logged in to use from the Intent Extension.
To configure the Intent Extension's Info.plist
file, double-click it in the Solution Explorer to open it for editing. Next, switch to the Source view then expand the NSExtension
and NSExtensionAttributes
keys in the editor:
Expand the IntentsSupported
key and add the name of any Intent Class this extension will support. For the example MonkeyChat app, it supports the INSendMessageIntent
:
If the app optionally requires that the user be logged on to the device to use a given intent, expand the IntentRestrictedWhileLocked
key and add the Class names of the Intents that have restricted access. For the example MonkeyChat app, the user must be logged in to send a chat message so we have added INSendMessageIntent
:
For a complete list of available Intent Domains, please see Apple's Intent Domains Reference.
Configuring the Main Class
Next, the developer will need to configure the main class that acts as the main entry point for the Intent Extension into Siri. It must be a subclass of INExtension
which conforms to the IINIntentHandler
delegate. For example:
using System;
using System.Collections.Generic;
using Foundation;
using Intents;
namespace MonkeyChatIntents
{
[Register ("IntentHandler")]
public class IntentHandler : INExtension, IINSendMessageIntentHandling, IINSearchForMessagesIntentHandling, IINSetMessageAttributeIntentHandling
{
#region Constructors
protected IntentHandler (IntPtr handle) : base (handle)
{
// Note: this .ctor should not contain any initialization logic.
}
#endregion
#region Override Methods
public override NSObject GetHandler (INIntent intent)
{
// 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 this;
}
#endregion
...
}
}
There is one solitary method that the app must implement on the Intent Extension main class, the GetHandler
method. This method is passed an Intent by SiriKit and the app must return an Intent Handler that matches the type of the given Intent.
Since the example MonkeyChat app only handles one intent, it is returning itself in the GetHandler
method. If the extension handled more than one intent, the developer would add a class for each intent type and return an instance here based on the Intent
passed to the method.
Handling the Resolve Stage
The Resolve Stage is where the Intent Extension will clarify and validate parameters passed in from Siri and have been set via the user's conversation.
For each parameter that gets sent from Siri, there is a Resolve
method. The app will need to implement this method for every parameter that the app might need Siri's help to get the correct answer from the user.
In the case of the example MonkeyChat app, the Intent Extension will require a one or more recipients to send the message to. For each recipient in the list, the extension will need to do a contact search that can have the following outcome:
- Exactly one matching contact is found.
- Two or more matching contacts are found.
- No matching contacts are found.
Additionally, MonkeyChat requires content for the body of the message. If the user has not provided this, Siri needs to prompt the user for the content.
The Intent Extension will need to gracefully handle each of these cases.
[Export ("resolveRecipientsForSearchForMessages:withCompletion:")]
public void ResolveRecipients (INSendMessageIntent intent, Action<INPersonResolutionResult []> completion)
{
var recipients = intent.Recipients;
// If no recipients were provided we'll need to prompt for a value.
if (recipients.Length == 0) {
completion (new INPersonResolutionResult [] { (INPersonResolutionResult)INPersonResolutionResult.NeedsValue });
return;
}
var resolutionResults = new List<INPersonResolutionResult> ();
foreach (var recipient in recipients) {
var matchingContacts = new INPerson [] { recipient }; // Implement your contact matching logic here to create an array of matching contacts
if (matchingContacts.Length > 1) {
// We need Siri's help to ask user to pick one from the matches.
resolutionResults.Add (INPersonResolutionResult.GetDisambiguation (matchingContacts));
} else if (matchingContacts.Length == 1) {
// We have exactly one matching contact
resolutionResults.Add (INPersonResolutionResult.GetSuccess (recipient));
} else {
// We have no contacts matching the description provided
resolutionResults.Add ((INPersonResolutionResult)INPersonResolutionResult.Unsupported);
}
}
completion (resolutionResults.ToArray ());
}
[Export ("resolveContentForSendMessage:withCompletion:")]
public void ResolveContent (INSendMessageIntent intent, Action<INStringResolutionResult> completion)
{
var text = intent.Content;
if (!string.IsNullOrEmpty (text))
completion (INStringResolutionResult.GetSuccess (text));
else
completion ((INStringResolutionResult)INStringResolutionResult.NeedsValue);
}
For more information, please see our The Resolve Stage Reference and Apple's Resolving and Handling Intents Reference.
Handling the Confirm Stage
The Confirm Stage is where the Intent Extension checks to see that it has all of the information to fulfill the user's request. The app wants to send confirmation along will all the supporting details of what is about to happen to Siri so it can be confirmed with the user that this is the intended action.
[Export ("confirmSendMessage:completion:")]
public void ConfirmSendMessage (INSendMessageIntent intent, Action<INSendMessageIntentResponse> completion)
{
// Verify user is authenticated and the app is ready to send a message.
...
var userActivity = new NSUserActivity (nameof (INSendMessageIntent));
var response = new INSendMessageIntentResponse (INSendMessageIntentResponseCode.Ready, userActivity);
completion (response);
}
For more information, please see our The Confirm Stage Reference.
Processing the Intent
This is the point where the Intent Extension actually performs the task to fulfill the user's request and pass the results back to Siri so the user can be informed.
public void HandleSendMessage (INSendMessageIntent intent, Action<INSendMessageIntentResponse> completion)
{
// Implement the application logic to send a message here.
...
var userActivity = new NSUserActivity (nameof (INSendMessageIntent));
var response = new INSendMessageIntentResponse (INSendMessageIntentResponseCode.Success, userActivity);
completion (response);
}
public void HandleSearchForMessages (INSearchForMessagesIntent intent, Action<INSearchForMessagesIntentResponse> completion)
{
// Implement the application logic to find a message that matches the information in the intent.
...
var userActivity = new NSUserActivity (nameof (INSearchForMessagesIntent));
var response = new INSearchForMessagesIntentResponse (INSearchForMessagesIntentResponseCode.Success, userActivity);
// Initialize with found message's attributes
var sender = new INPerson (new INPersonHandle ("sarah@example.com", INPersonHandleType.EmailAddress), null, "Sarah", null, null, null);
var recipient = new INPerson (new INPersonHandle ("+1-415-555-5555", INPersonHandleType.PhoneNumber), null, "John", null, null, null);
var message = new INMessage ("identifier", "I am so excited about SiriKit!", NSDate.Now, sender, new INPerson [] { recipient });
response.Messages = new INMessage [] { message };
completion (response);
}
public void HandleSetMessageAttribute (INSetMessageAttributeIntent intent, Action<INSetMessageAttributeIntentResponse> completion)
{
// Implement the application logic to set the message attribute here.
...
var userActivity = new NSUserActivity (nameof (INSetMessageAttributeIntent));
var response = new INSetMessageAttributeIntentResponse (INSetMessageAttributeIntentResponseCode.Success, userActivity);
completion (response);
}
For more information, please see our The Handle Stage Reference.
Adding an Intents UI Extension
The optional Intents UI Extension presents the opportunity to bring the app's UI and branding into the Siri experience and make the users feel connected to the app. With this extension the app can bring the brand as well as visual and other information into the transcript.
Just like the Intents Extension, the developer will do the following step for the Intents UI Extension:
- Add an Intents UI Extension project to the Xamarin.iOS app solution.
- Configure the Intents UI Extension
Info.plist
file. - Modify the Intents UI Extension main class.
For more information, please see our The Intents UI Extension Reference and Apple's Providing a Custom Interface Reference.
Creating the Extension
To add an Intents UI Extension to the solution, do the following:
Right-click on the Solution Name in the Solution Pad and select Add > Add New Project....
From the dialog box select iOS > Extensions > Intent UI Extension and click the Next button:
Next enter a Name for the Intent Extension and click the Next button:
Finally, click the Create button to add the Intent Extension to the apps solution:
In the Solution Explorer, right-click on the References folder of the newly created Intent Extension. Check the name of the common shared code library project (that the app created above) and click the OK button:
Configuring the Info.plist
Configure the Intents UI Extension's Info.plist
file to work with the app.
Just like any typical App Extension, the app will have the existing keys of NSExtension
and NSExtensionAttributes
. For an Intents Extension there is one new attribute that must be configured:
IntentsSupported is required and consists of an array of Intent Class names that the app want to support from the Intent Extension.
To configure the Intent UI Extension's Info.plist
file, double-click it in the Solution Explorer to open it for editing. Next, switch to the Source view then expand the NSExtension
and NSExtensionAttributes
keys in the editor:
Expand the IntentsSupported
key and add the name of any Intent Class this extension will support. For the example MonkeyChat app, it supports the INSendMessageIntent
:
For a complete list of available Intent Domains, please see Apple's Intent Domains Reference.
Configuring the Main Class
Configure the main class that acts as the main entry point for the Intent UI Extension into Siri. It must be a subclass of UIViewController
which conforms to the IINUIHostedViewController
interface. For example:
using System;
using Foundation;
using CoreGraphics;
using Intents;
using IntentsUI;
using UIKit;
namespace MonkeyChatIntentsUI
{
public partial class IntentViewController : UIViewController, IINUIHostedViewControlling
{
#region Constructors
protected IntentViewController (IntPtr handle) : base (handle)
{
// Note: this .ctor should not contain any initialization logic.
}
#endregion
#region Override Methods
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Do any required interface initialization here.
}
public override void DidReceiveMemoryWarning ()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
#endregion
#region Public Methods
[Export ("configureWithInteraction:context:completion:")]
public void Configure (INInteraction interaction, INUIHostedViewContext context, Action<CGSize> completion)
{
// Do configuration here, including preparing views and calculating a desired size for presentation.
if (completion != null)
completion (DesiredSize ());
}
[Export ("desiredSize:")]
public CGSize DesiredSize ()
{
return ExtensionContext.GetHostedViewMaximumAllowedSize ();
}
#endregion
}
}
Siri will pass a INInteraction
class instance to the Configure
method of the UIViewController
instance inside of the Intent UI Extension.
The INInteraction
object provides three key pieces of information to the extension:
- The Intent object being processed.
- The Intent Response object from the
Confirm
andHandle
methods of the Intent Extension. - The Interaction State that defines the state of the interaction between the app and Siri.
The UIViewController
instance is the principle class for the interaction with Siri and because it inherits from UIViewController
, it has access to all of the features of UIKit.
When Siri calls the Configure
method of the UIViewController
it passes in a View Context stating that the View Controller will either be hosted in a Siri Snippit or Maps Card.
Siri will also pass in a completion handler that the app need to return the desired size of the view once the app have finished configuring it.
Design the UI in iOS Designer
Layout the Intents UI Extension's user interface in the iOS Designer. Double-click the extension's MainInterface.storyboard
file in the Solution Explorer to open it for editing. Drag in all of the required UI elements to build out the User Interface and save the changes.
Important
While it is possible to add interactive elements such as UIButtons
or UITextFields
to the Intent UI Extension's UIViewController
, these are strictly forbidden as the Intent UI in non-interactive and the user will not be able to interact with them.
Wire-Up the User Interface
With the Intents UI Extension's User Interface created in iOS Designer, edit the UIViewController
subclass and override the Configure
method as follows:
[Export ("configureWithInteraction:context:completion:")]
public void Configure (INInteraction interaction, INUIHostedViewContext context, Action<CGSize> completion)
{
// Do configuration here, including preparing views and calculating a desired size for presentation.
...
// Return desired size
if (completion != null)
completion (DesiredSize ());
}
[Export ("desiredSize:")]
public CGSize DesiredSize ()
{
return ExtensionContext.GetHostedViewMaximumAllowedSize ();
}
Overriding the Default Siri UI
The Intents UI Extension will always be displayed along with other Siri content such as the app icon and name at the top of the UI or, based on the Intent, buttons (like Send or Cancel) may be displayed at the bottom.
There are a few instances where the app can replace the information that Siri is displaying to the user by default, such as messaging or maps where the app can replace the default experience with one tailored to the app.
If the Intents UI Extension needs to override elements of the default Siri UI, the UIViewController
subclass will need to implement the IINUIHostedViewSiriProviding
interface and opt-in to displaying a particular interface element.
Add the following code to the UIViewController
subclass to tell Siri that the Intent UI Extension is already displaying the message contents:
public bool DisplaysMessage {
get {return true;}
}
Considerations
Apple suggests that the developer take the following considerations into account when designing and implementing the Intent UI Extensions:
- Be Conscious of Memory Usage - Because Intent UI Extensions are temporary and only shown for a short time, the system imposes tighter memory constraints than are used with a full app.
- Consider Minimum and Maximum View Sizes - Ensure that the Intent UI Extensions looks good on every iOS device type, size and orientation. Additionally, the desired size that the app sends back to Siri might not be able to be granted.
- Use Flexible and Adaptive Layout Patterns - Again, to ensure that the UI looks great on every device.
Summary
This article has covered SiriKit and shown how it can be added to the Xamarin.iOS apps to provide services that are accessible to the user using Siri and the Maps app on an iOS device.