App Development

A Deeper Look at SiriKit for iOS 10

blog-featured-image wwdc PB-510x296

iOS 10 brings a host of amazing features, many of which are for developers. One of these great features is SiriKit. Using SiriKit, developers now have the ability to utilize Apple’s powerful voice companion to interact with their apps. Ben Frye, one of my iOS teammates, gives a great SiriKit overview here. In my post, I’ll explore the strengths and weaknesses of the new SiriKit API by creating a simple messaging app using SiriKit, named MessageBot.

Intents

The SiriKit API is driven by Intents. Intents are the basis of your app’s communication with SiriKit. Intents are what direct the user’s request from Siri directly to your app, which will allow you to respond to the user’s request. An Intent represents a user’s intention. For example, an intent for sending a message might include the recipient and the body of the message, and the application that should send the message request.

Each type of Intent has an associated protocol that its handler must adopt. The methods of this protocol are divided into three groups: resolution methods, confirmation methods, and handling methods. (We’ll be discussing these later.)

Intent Handling

Supporting Siri in your application begins by adding a new Intents Extension. Xcode 8 helpfully provides a template for adding new Intents Extensions , which will do the work of handling Siri’s requests. This is Siri’s entry point into our application. The IntentHandler acts like a dispatcher, responsible for resolving and handling all requests from Siri.

In this case, we simply return an object that can handle the INSendMessageIntent:

class IntentHandler: INExtension {
    
    
    override func handler(for intent: INIntent) -> AnyObject? {
       // If this is an Intent to send a message return our message handler 
       if intent is INSendMessageIntent {
            return MessageHandler()
        }
        return nil
    }
 }

SiriKit is adopted by adding Extension Attributes to your Intents Extension’s plist. These attributes let Siri know what types of services your app can perform. They also correspond to defined protocols your handler must adopt. In our case, we are only sending a message, so we only need to add INSendMessageIntent to the IntentsSupported key of our plist, and have our handler conform to INSendMessageIntentHandling.

INSendMessageIntentHandling defines seven methods for resolving, confirming, and handling messages, only one of which is required.

public func handle(sendMessage intent: INSendMessageIntent, completion:
(INSendMessageIntentResponse) -> Void)

Your messaging service must handle sending the message. SiriKit does the rest. If you decide to implement the remaining methods, they will be called at different points of the message transaction. You have the option to intervene at different phases. If you want to check the integrity of the message before sending, ask the user for clarification, or disambiguate the recipients, you have several entry points to do so.

Flow of Events

Let’s take a look at the order of events for a typical transaction.

If the user says to Siri, “Send a message to Mike with MessageBot, saying ‘What time is lunch?’” Siri will check for your Intent Handler to see if it knows of an object that can respond to the user’s request, in our case we return an instance of MessageHandler().

override func handler(for intent: INIntent) -> AnyObject? {
        if intent is INSendMessageIntent {
            return MessageHandler()
        }
        
        
        return nil
    }

Since our MessageHandler conforms to INSendMessageIntentHandling, SiriKit now has all it needs in order to send a message via your extension, but will still attempt to call all other resolution, confirmation, and dispatching methods.

Resolution

After the MessageHandler instance is received, we have the option to resolve any issues with the recipients, or the message itself.

We can handle recipient resolution like so:

// Resolve the recipients of this message
     func resolveRecipients(forSendMessage intent: INSendMessageIntent, with 
completion: ([INPersonResolutionResult]) ->Void) {


        if let recipients = intent.recipients {
            var resolutionResults = [INPersonResolutionResult]()


            for recipient in recipients {
             let matchingContacts = addressBook.users(matching: 
recipient.displayName)


              switch matchingContacts.count {
            
            
              // If there is more than 1 recipient, ask the user to disambiguate 
              case 2 ... Int.max:
               let disambiguationOptions: [INPerson] = matchingContacts.map { 
contact in
              return contact.inPerson()
              }
              resolutionResults += 
[INPersonResolutionResult.disambiguation(with: disambiguationOptions)]


             case 1:
             let recipientMatched = matchingContacts[0].inPerson()
             resolutionResults += [INPersonResolutionResult.success(with: 
recipientMatched)]


             case 0:
                resolutionResults += 
[INPersonResolutionResult.unsupported(with: 
INIntentResolutionResultUnsupportedReason.none)]


              ...
                 }
            }
            completion(resolutionResults)


        } else {
	    // The user didn’t supply a recipient, ask them to give us one
            completion([INPersonResolutionResult.needsValue()])
        }
    }

In resolveRecipients, we have the option to check the user’s requested recipients, see if they’re in our address book, ask the user to disambiguate if needed, or handle any other issue that may occur.

We let the system know of our success or any issues that occurred by using the supplied completion closure, which we supply with an array of INPersonResolutionResult method calls shown above.

Resolution results are dependent on the content you’re resolving. For example, if we’re resolving the contents of the message, we would use the INStringResolutionResult class closure. If an issue occurred or we need more clarification, passing an INIntentResolutionResult method call back via the closure will trigger Siri to ask the user to disambiguate their query, in which case your implementation of the corresponding function will be called. In the event that the query can’t be properly resolved, Siri will alert the user to continue inside your application.

Confirmation

After the contents and recipients of the message are resolved, you have a last chance do any checks or customization that may be necessary using:

func confirm(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) 
->Void)

…which will return the contents of the message in the form of the user’s Intent.

Handling

If we’ve successfully resolved and confirmed our message, we must handle the transmission of the message:

func handle(sendMessage intent: INSendMessageIntent, completion: 
(INSendMessageIntentResponse) -> Swift.Void) {
       // Check the message and recipient integrity one last time before we sent the 
message
      guard let message = intent.content, recipients = intent.recipients else {


            completion(INSendMessageIntentResponse.init(code: 
INSendMessageIntentResponseCode.failure, userActivity: nil))
            return
        }
        if messageService.sendMessage(message: message, to: recipients) {
            completion(INSendMessageIntentResponse.init(code: .success, userActivity: nil))
        } else {
            completion(INSendMessageIntentResponse.init(code: 
INSendMessageIntentResponseCode.failure, userActivity: nil))
        }
    }

Conclusion

SirKit opens up an incredibly powerful platform to developers who’ve been asking for access to Siri since it first launched in 2011. Apple’s solution to responding to user Intents is orderly and intuitive. SiriKit does, however, leave something to be desired. With SiriKit being limited to only six categories of apps, many apps won’t have the luxury of utilizing the new API. It’s impossible to say how Siri will be opened up further to developers in the future. As it stands, however, SiriKit is a huge stepping stone in Apple’s endeavor to make iOS more open. It’ll be exciting to see how iOS developers utilize SiriKit when iOS 10 is made available this fall.

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article