Siri is Apple’s personal virtual assistant which allows you to interact with your device via voice. SiriKit supports certain domains like VoIP calling, Messaging, Car Commands, Payments, Ride Booking, and Workouts. Within each domain, there is a series of intents, which are actions Siri can perform. For example, in Messaging domain, there are intents like sending a message, setting attributes on a message and searching for messages. In this blog, we are going to send a message without opening the app using Siri.
Let’s get started
First, create a simple view application.
Adding an intent extension to Xcode Project
Now go to File > New > Target . Then select Intents Extension from the list.
After pressing Next button, Enter the name of the extension and then click finish button.
After the finish button is pressed, you will also see the following popup. This popup indicates you to activate the newly added MessageaIntent scheme to use build and debug. So Activate this scheme.
After activating above scheme, you will see the following popup for asking the same activation of scheme MessageIntentUI. So again activate this scheme too.
You will see MessageIntent and MessageIntentUI in file navigator. MessageIntent extension handles all Siri features and MessageIntentUI extension use for displaying user interface.
Enable Siri and App Groups
Go to the Capabilities of main app and enable Siri as follow
Enable App Groups in all three targets as follow.
If there is no group id is displayed after enabling App Groups, you can manually add from Entitlements file.
In Info.plist file of MessageIntent Extension, you can see NSExtensionAttributes under NSExtension. Here three Intents are available.
INSendMessageIntent – use for sending messages
INSearchForMessagesIntent – use for searching messages
INSetMessageAttributeIntent – use for setting message content attributes.
You can edit the IntentsSupported as per your requirements.
IntentHandler File
You can see the INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling protocol and its method implemented in IntentHandler file. In this demo, we will implement send message feature so here we need only INSendMessageIntentHandling. so you can remove the other two and its methods.
We are going to make an app in which first Siri will ask about the contact to whom we want to send a message. After that Siri will ask about the message which we want to send. And then after approval, a message will be sent.
First of all, we need to access Contact for the searching name in our contact list.
Add above key in Info.plist file. Now write following method for search contact from name.
func searchContact(recipients : [INPerson], completionHandler: @escaping ([INPerson]?) -> Void) { let contactStore = CNContactStore() var finalContacts = [INPerson]() if recipients.count == 0 { completionHandler(nil) return } for objRecipient in recipients { do { let predicate = CNContact.predicateForContacts(matchingName: objRecipient.displayName) let filteredContacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: [CNContactFamilyNameKey as CNKeyDescriptor, CNContactMiddleNameKey as CNKeyDescriptor, CNContactGivenNameKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor]) for objContact in filteredContacts { if let objFilterContact = (recipients.filter{ ($0.displayName == objContact.givenName || $0.displayName == objContact.familyName || $0.displayName == objContact.middleName)}).first { finalContacts.append(objFilterContact) } } completionHandler(finalContacts) } catch { print("unable to fetch contacts") completionHandler(nil) } } }
In this method, recipients array are passed as arguments and we search with contact name from our contact list. And return final contact list array.Now we will call this method in the below method for getting the correct recipient.
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) { if let recipients = intent.recipients { self.searchContact(recipients: recipients) { (finalRecipients) in // If no recipients were provided we'll need to prompt for a value. if finalRecipients == nil { completion([INSendMessageRecipientResolutionResult.needsValue()]) return } if finalRecipients?.count == 0 { completion([INSendMessageRecipientResolutionResult.needsValue()]) return } var resolutionResults = [INSendMessageRecipientResolutionResult]() // Implement your contact matching logic here to create an array of matching contacts switch finalRecipients!.count { case 2 ... Int.max: // We need Siri's help to ask user to pick one from the matches. if intent.recipients![0].siriMatches == nil { resolutionResults += [INSendMessageRecipientResolutionResult.success(with: intent.recipients![0])] } else { resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: finalRecipients!)] } case 1: // We have exactly one matching contact resolutionResults += [INSendMessageRecipientResolutionResult.success(with: finalRecipients![0])] case 0: // We have no contacts matching the description provided resolutionResults += [INSendMessageRecipientResolutionResult.unsupported()] default: break } completion(resolutionResults) } } }
Resolve Content Method
This method will be called when you speak your contact name. As per the above method, we search for the matching contact and depend on the matches found Siri will perform an action as per our requirement.
-> If there are more than two matches found then Siri will ask about which contact you want to send a message.
-> If there is one match found then Siri will ask about what message you want to send.
-> Also if there is no match found then Siri will again ask about the contact you want to send a message.
When you speak message then following method will be called.
func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) { if let text = intent.content, !text.isEmpty { completion(INStringResolutionResult.success(with: text)) } else { completion(INStringResolutionResult.needsValue()) } }
If the text is empty then you need to ask about the text. And if there is a valid text then success will be called. Once a resolution is completed, perform validation on the intent confirmation in the following method. This is an optional method.
func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { // Verify user is authenticated and your app is ready to send a message. let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity) completion(response) }
After all this, Siri will ask to send a message. If you say Yes then below method will be called and you have to handle data in this method.
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { // Implement your application logic to send a message here. let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity) let dictMessage = NSMutableDictionary() dictMessage.setObject(intent.content!, forKey: "message" as NSCopying) dictMessage.setObject(intent.recipients![0].displayName, forKey: "recipient" as NSCopying) UserDefaults(suiteName: "group.logistic.test")?.set(dictMessage, forKey: "dictMessage") completion(response) }
This is a required method. A dictionary is created for storing message and recipient data in UserDefaults so that we can access this data in our main app through app groups. Now we are done with IntentHandler file and will create the UI for message display.
You can create your message UI as per your requirement in MainInterface Storyboard of MessageIntentUI folder. Here I have created a simple message display view with label outlet.
IntentViewController File
Below method will be called when Siri asked about the message and display the final message. so set your label’s value in this method.
func configure(with interaction: INInteraction, context: INUIHostedViewContext, completion: @escaping (CGSize) -> Void) { let intent = interaction.intent as! INSendMessageIntent lblMessage.text = intent.content //configure your UI let size = CGSize.init(width: lblMessage.frame.width + 50, height: lblMessage.frame.height + 80) completion(size) }
Now only message and recipient name fetch and display are remaining in our main app. For that, you need to get data from extension to your app. When you open the app, applicationDidBecomeActive method will be called in Appdelegate. So write the code as below to notify the view controller about the latest message dictionary.
func applicationDidBecomeActive(_ application: UIApplication) { if UserDefaults(suiteName: "group.logistic.test")?.object(forKey: "dictMessage") != nil { NotificationCenter.default.post(name: NSNotification.Name("MessageSent"), object: nil, userInfo: nil) } }
ViewController File
Take outlet of a message and recipient name label for displaying value. Then add and remove observer as below.
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) NotificationCenter.default.addObserver(self, selector: #selector(setMessageValue), name: NSNotification.Name(rawValue: "MessageSent"), object: nil) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self) }
Write a function for setting data.
@objc func setMessageValue() { if let dictMessage = UserDefaults(suiteName: "group.logistic.test")?.object(forKey: "dictMessage") { lblMsg.text = (dictMessage as! NSDictionary).object(forKey: "message") as? String lblRecipientName.text = (dictMessage as! NSDictionary).object(forKey: "recipient") as? String } }
Now run your project with SiriMessageDemo target selected.
Then stop the app and select MessageIntent as a target.
After pressing the run button following popup will be displayed
Now choose your SiriMessageDemo app for run.
The following screen will be opened after some seconds or you can also open Siri by long pressing home button.
Now command Siri by saying something like “Send message using SiriMessageDemo” and the following screen will be opened.
Then speak the contact name which you want to send a message. Then contact permission alert will be opened and press OK.
After this, Siri will ask about a message.
Speak the message which you want to send. Then Siri will display the message in UI which you provide in MessageIntentUI.
You can either press send button or speak YES. Siri will send the message.
Now open the application. You can see the following screen with setting Recipient name and last message data.
Here we have displayed data very simply. You can set as per your requirement.