Hey Siri, what’s cooking?

SiriKit Audio Calling tutorial on iOS

Anup D'Souza

--

Just as Iron Man has Jarvis, so does iOS have Siri. Siri enables voice interactions with iOS apps in a conversational manner that feels natural to users. Wouldn’t it then be great to take advantage of this feature to add some cool features to your app?

In the following tutorial, we’ll be creating an app to call your favourite superheroes. While you’d expect Siri to function like Jarvis, it is limited to certain domains in terms of functionality (for now). It is therefore important to remember what’s supported & what isn’t while you plan the development of your dream app if you wish to integrate Siri. We’ll be developing within the VoIP Calling domain which is supported by Siri. Some other domains Siri could be used for are Messaging, Payments, Ride Booking etc.

Begin by creating a new Single View Xcode project and name it whatever you like, I’ve named the project SiriDemo. Next, we need to enable Siri Capabilities for our app, so go to

Project > Targets > SiriDemo > Capabilities > Siri

& turn it ON. You should now see a new SiriDemo.entitlements file added to your project.

Enabling Siri Capabilities

Don’t worry about this just yet but instead, head over to the lone Info.plist file and add a descriptive message to display to the user when requesting access to Siri from your app. Simply add the Privacy — Siri Usage Description key with any message that you’d like, I’ve set the value to “$(PRODUCT_NAME) would like to access Siri” just so that if I change my app name later, the message would still be hold.

Next, we’ll add what is essentially the crux of how Siri ties in with your application. Since Siri works as an extension to your app, you need to add an intents extension to allow Siri to be able to process the user’s commands. The way Siri does this is using Intents which essentially:

  1. Resolve the request being made with Siri
  2. Confirm the request is as it should be and finally
  3. Handle the request appropriately.

Thing’s will be clearer as we progress further so don’t fret if all this doesn’t make much sense yet. Select the (+) button below the SiriDemo target in Xcode to add a new Intents Extension. Be careful not to select Intents UI Extension. Give it a name such as SiriExtension and click Finish. If you get a popup asking to activate this new scheme you can click Cancel. Clicking Activate basically sets the extension as the active Scheme, nothing too big. You will now see a new SiriExtension group added to your project with an IntentHandler.swift file & another Info.plist file.

Adding Intents Extension for Siri

What we need to do next is to allow your app & Siri to share data for instance, if you were to ask Siri to call someone using your app, it is the responsibility of the app to supply the information for that person in order to….. you guessed it, resolve the call request you made to Siri. Since your app & the extension need to access some common data for this purpose, we will invoke the power of ‘App Groups’ to do so. Just as you enabled Siri capability on your Target, do the same for app groups; so go to:

Project > Targets > SiriDemo > Capabilities > App Groups

& turn it ON. Add a new container by clicking the + button and give it a title such as group.{bundle_identifier} for simplicity. Click the checkbox to enable it if it isn’t already. Repeat this same step for the SiriExtension target as well. If you now view each of the entitlement files in your project viz. SiriDemo.entitlements & SiriExtension.entitlements, you will see the app group added to them. The entitlement files basically define the capabilities of the application which in this case are Siri & App Groups.

Enabling App Groups for App Target, same to be done for the extension too

Next, go to the Info.plist file under SiriExtension and expand the NSExtensionAttributes key under NSExtension key, then expand the IntentsSupported key. You’ll find 3 supported intent items here which are added by default. As we won’t be using these, click the minus (-) sign next to each intent item to remove them and add the following intent item: ‘INStartAudioCallIntent’. Note here that the IntentsRestrictedWhileLocked key contains 0 items; this key specifies which intents are not supported while the device is locked and require the user to unlock the device. This is handy if for instance, you want to implement Payment intents and initiate payments using Siri.

Moving on, open your ViewController.swift file and in viewDidLoad(), add the following piece of code:

Here we request access to Siri when the app launches and loads the View Controller. Make sure to also import the Intents framework at the top of the file as well. If you launch the app now, you should see a nice little alert asking for access to Siri whilst displaying the descriptive message we added earlier to the app’s Info.plist file. Make sure to allow access. Now open the IntentHandler.swift file under SiriExtension group and clear all the code within the IntentHandler class body i.e. remove everything between {}. Also remove all the message intent protocol conformance and make IntentHandler conform to only INExtension & INStartAudioCallIntentHandling protocols. Your IntentHandler.swift file should look like this:

The INStartAudioCallIntentHandling protocol has only one required method to handle/execute the intent aptly named handle(). We will however add two other optional methods, resolveContacts() and confirm() which as you will have guessed it; resolve the intent & confirm the intent for processing. resolveContacts() is of importance to note here for this is where we will process the data required for Siri to later perform actions upon. Add the following method stubs, you will get an error for DataManager if you build now; we’ll fix this shortly.

In resolveContacts() we receive the invoking intent via voice commands to Siri and we then extract the contact display name if there is one to search for within our app data. Since we haven’t added any data as yet to the app, let’s do that now. Add a new Swift file and name it DataManager.swift. We’ll use this class to save some contact info and also process it when required by Siri. Use the following code:

There are only 2 methods of note, saveContacts() which saves a simple array of dictionaries containing String type key values of contact information and findContact() which takes a contact name as input and returns an array of one or more matching contacts as INPerson(s). We use basic string matching to check if the input contact name is found even as a partial match with any of the saved contacts. Note here that the saving and retrieving of data is being done using UserDefaults with a suiteName using the AppGroup’ title we created earlier. This allows us to extract data in the extension in findContact() which was saved by the app in saveContacts(). It would probably be better to create a Contact struct for saving with properties for name & number; that’s for another day. Let’s store some data now, so open up ViewController.swift file and update viewDidLoad() like so:

We pass an array of dictionaries containing String key-value pairs of name & number to the DataManager class for saving into UserDefaults, nothing too fancy. Build the app now & it should build without any errors. Update the resolveContacts() method in IntentHandler.swift like so:

The implementation is fairly straight forward, for the matching contacts received from the findContact() method, we respond with either success if a single matching contact was found, disambiguation if multiple matching contacts were found or unsupported if there were no matching contacts found for simplicity. In cases of disambiguation, Siri will present the user with the multiple contacts found to further select the correct one automatically! Magical eh?

Finally, in order to have our app gain control from Siri to make an actual call, add the following AppDelegate extension inside AppDelegate.swift:

The handle() method inside IntentHandler will invoke your app passing it the intent containing contact information to place the actual call. From this intent we fetch the contact phone number and further using the open api, dial the phone number.

Build and run the app scheme so that the ViewController loads and saves data to UserDefaults, next run the extensions scheme and run it; choose Siri as the app to run when presented with a prompt to choose an app. If you do this on a simulator or device, Siri will automatically be launched and wait for voice input. This is where you can try saying the following, “Call Mario using SiriDemo”. Siri will process the command using the IntentHandler in resolve-confirm-handle fashion and respond with failure. Next say, “Call Bruce using SiriDemo” and Siri will then ask you which of Hulk vs Batman you’d like to call. Depending on your Marvel vs DC allegiance, summon your favourite superhero.

Personal tip: If you can call Batman, always call Batman.

Bonus tip: If you get tired with the repetitive summoning, you can have Siri do it for you by selecting Edit Scheme for SiriExtension and typing out your command in the box for Siri Intent Query. Now onwards, every time you launch the extension, Siri will automatically process that as the voice input command.

If you’d like to read more about SiriKit, here’s a good place to begin. The complete source code can be found here.

This post has been a little lengthy so I’ve tried to keep it as concise as possible. Feel free to experiment with SiriKit & in case you have any questions or feedback; drop them in the comments.

Consider subscribing to my YouTube channel & follow me on X(Twitter) for more such information. Leave a comment if you have any questions.

Clap 👏 & share this article if you found it useful!

Also, check out some of my other articles:

* Get started with Gemini AI on iOS with SwiftUI

* Text & Image (multimodal) chat with Gemini AI on iOS

* Multi-turn Chat with Gemini AI on iOS (SwiftUI Tutorial)

Stackademic

Thank you for reading until the end. Before you go:

--

--