--
“Hey Siri…”
Adding in support for Siri to Significant Other has been one of my highlights to work on thus far. Since the announcement, and subsequent updates to SiriKit in iOS, the ability for developers to leverage this technology in order to add a whole new dimenion to their app(s) has been a frontier that I wanted to venture into.
In order to take advantage of Siri within your app it must fall into one more Intent domains.
SiriKit defines the types of requests — known as intents — that users can make. Related intents are grouped into domains to make it clear which intents you might support in your app. For example, the messages domain has intents for sending messages, searching for messages, and marking messages as read or unread.
Significan Other uses the INMessaging domain. Within the INMessaging domain there are three possible handlers defined by the INMessagesDomainHandling
protocol.
- INSendMessageIntent: Compose and send a message to one ore more recipients
- INSearchForMessagesIntent: Search for messages based upon query parameters
- INSetMessageAttributeIntent: Request to modify a message. i.e. Mark a message as read.
I only needed support for INSendMessageIntent
because you can’t search for messages and marking message as read isn’t a feature that I’ve implemented yet.
Siri Setup
After creating the Intent Extension and the Intent UI targets I decided to tackle customizing the Siri UI. While it is possibly to create a customized UI for your extension that will replace the default interface, I needed to do one “simple” thing…remove the “To:” field. There isn’t a need for it because you only have one Significant Other. Less is more.
The main work horse for this, as well as, other customizing tasks is:
optional func configureView(for parameters: Set<INParameter>,
of interaction: INInteraction,
interactiveBehavior: INUIInteractiveBehavior,
context: INUIHostedViewContext,
completion: @escaping (Bool, Set<INParameter>, CGSize) -> Void)
There are two points of logic that needed to implemented. First, removing the “To:” field as stated above and second I needed to insure that the content size fit the message width and height.
The configureView...
method is called numerous times for each of the parameters.
Each interface presented by Siri or Maps contains information from the original intent object or from your response. Before constructing the interface, SiriKit builds a list of properties from those objects that it intends to display. For example, when displaying information about a booked ride, SiriKit might want to display the estimated pickup time, the pickup location, and the driver’s name and picture from your response object. It wraps each property in an INParameter object, which you use to retrieve the value of the property later.
By checking for the parameters, content / recipients, I was able to make my customizations.
let recipients: INParameter = INParameter(for: INSendMessageIntent.self,
keyPath: #keyPath(INSendMessageIntent.recipients))
let content: INParameter = INParameter(for: INSendMessageIntent.self,
keyPath: #keyPath(INSendMessageIntent.content))
if parameters == [recipients] {
if #available(iOS 12.0, *) {
completion(true, parameters, .zero)
} else {
completion(true, parameters, self.minimumSize)
}
} else if parameters == [content] {
if #available(iOS 12.0, *) {
completion(false, parameters, self.desiredSize)
} else {
completion(true, parameters, .zero)
}
} else {
completion(false, [], .zero)
}
Full disclosure
The above snippet is 95% logic and 5% trial and error. The docs state the completion block parameters indicate true / false as to whether you configured your view controller. Specifing true
indicates that your custom interface is ready to be displayed. Specifing false
will display the default UI for the specified parameters. However, this behavior didn’t work across both iOS 12 and 11. I worked on this for hours trying to track down the differences to no avail. I’m not sure if this a bug in iOS 11 and / or 12. Bug report is coming shortly.
The last piece to get the customizations to show up is conforming to INUIHostedViewSiriProviding
.
Implement this protocol in the view controller that you use to present custom content in your Intents UI extension. Siri supports the replacement of its map interface when displaying locations or the replacement of its message interface when showing the recipients and content of a message. Implementation of this protocol and its properties is optional.
extension IntentViewController: INUIHostedViewSiriProviding {
// Public (properties)
var displaysMessage: Bool {
return true
}
}
App Groups / KVS Store / CloudKit
With the UI and basic protocol implementation completed in the Intent and Intent UI it was time to handle the business logic. My strategy was to utilitze
public func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void)
which would, I thought, call the host delegate’s
func application(_ application: UIApplication,
handle intent: INIntent,
completionHandler: @escaping (INIntentResponse) -> Void)
Unfortnately, only Workout Intents get that functionality. While this wasn’t ideal I was able to use the numerous frameworks I had split the app’s functionality to reuse models and various business logic. Once CloudKit and App Groups were endabled I had to make sure that I added the same identifiers from the host app into the new targets or I wouldn’t be accessing the same datasource.
More to Come
I am very excited about this initial rollout of Siri support. Making it as easy as possible to share your secure / private messages to your Significant Other.