Facing issue in Siri Integration with custom intents - ios

I’m trying to integrate Siri Shortcuts to my application. The concept which I’m trying is to get reward points of my card with secret pin confirmation. Please find what I have done for this below.
Enabled Siri in capabilities and added Siri Intent definition file.
Added new custom intent named say Rewards.
Defined the title. Subtitle and params(accType, pin) with confirmation enabled. Pin will be sent separately to user.
Then defined the intent response with param ‘rewardPoints’ and defined the response messages.
Added Siri intent extensions.
Added custom intent to info.plist files within project and intent extension.
Verified and added new handler for the custom intent and define the resolve, handle and confirm methods as below. For now, I’m returning random no for reward points.
//
// RewardsIntentHandler.swift
// SiriIntentExt
//
import UIKit
import Intents
class RewardsIntentHandler: NSObject, RewardsIntentHandling {
func resolveAccType(for intent:RewardsIntent, with completion: #escaping ([INStringResolutionResult]) -> Void) {
guard let accType = intent.accType else {
completion([INStringResolutionResult.needsValue()])
return
}
completion([INStringResolutionResult.success(with: accType)])
}
func resolvePin(for intent:RewardsIntent, with completion: #escaping ([INIntegerResolutionResult]) -> Void) {
guard let verifyPin = intent.pin else {
completion([INIntegerResolutionResult.needsValue()])
return
}
completion([INIntegerResolutionResult.confirmationRequired(with: verifyPin as? Int)])
}
func confirm(intent: RewardsIntent, completion: #escaping (RewardsIntentResponse) -> Void) {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.ready, userActivity: nil))
}
func handle(intent: RewardsIntent, completion: #escaping (RewardsIntentResponse) -> Void) {
guard intent.accType != nil else {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.continueInApp, userActivity: nil))
return
}
guard intent.pin != nil else {
completion(RewardsIntentResponse.init(code: RewardsIntentResponseCode.continueInApp, userActivity: nil))
return
}
let response = RewardsIntentResponse.success(rewardPoints: NSNumber(value: 3453))
completion(response)
}
}
Modified the IntentHandler to return rewards handler for rewards intent
//
// IntentHandler.swift
// SiriIntentExt
//
import Intents
class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
if intent is RewardsIntent {
return RewardsIntentHandler()
}
return self
}
}
Donated the intent on view load as below.
//
// ViewController.swift
// Shortcuts
//
import UIKit
import Intents
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
siriAuthorisarion()
donateRewardIntent()
}
func siriAuthorisarion() {
INPreferences.requestSiriAuthorization { (status) in
print("Siri Authorization Status - ", status)
}
}
func donateRewardIntent() {
let rewardsIntent = RewardsIntent()
rewardsIntent.suggestedInvocationPhrase = "Reward Points"
rewardsIntent.accType = "test account"
let interaction = INInteraction(intent: rewardsIntent, response: nil)
interaction.donate { error in
if let error = error {
print("Donating intent failed with error \(error)")
}
DispatchQueue.main.async {
let alert = UIAlertController.init(title: ((error != nil) ? "Error" : "Success"), message: ((error != nil) ? "Oops!!! Error occured on donating intent." : "Intent donated succussfully!!!"), preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
}
I'm facing problem from the above code base. Siri is not requesting for pin and not able to get the exact reward points for the account.
Have following questions.
Can we add the intents programmatically to Siri instead adding from shortcuts app or settings. So that user can directly use the functionality once installing the application.
Once intent is added using Shortcuts app, I’m trying the ask Siri for reward points. Its immediately requesting for my app shortcuts defined. Once we say 'yes' to request, I need to be asked for pin. But Siri replies with some problem with my app. What to be done for asking for next param value.
In the handler file, I have added the resolve methods for each parameters. I feel, resolve methods are not getting called to validate the values. Do we need to handle anything to make resolve methods work?
How can I debug the handler implementation using breakpoint within resolve/handle/confirm methods.
Thanks in advance.

Find my analysis for the above questions.
Can we add the intents programmatically to Siri instead adding from shortcuts app or settings. So that user can directly use the functionality once installing the application.
By default, intents are provided for specific domains such as messaging, payments, photos, workout, etc. No need to explicitly add intents through shortcuts for theses specific domains. Apart from these domains if we are creating custom intent, we are in need to donate and add the intents to Siri using shortcut/settings application.
Once intent is added using Shortcuts app, I’m trying the ask Siri for reward points. Its immediately requesting for my app shortcuts defined. Once we say 'yes' to request, I need to be asked for pin. But Siri replies with some problem with my app. What to be done for asking for next param value.
From iOS13, Apple has added Siri parameters and Siri suggestion for custom intents to request the missing parameters. Till iOS12, we don't have parameters option for custom intents.
In the handler file, I have added the resolve methods for each parameters. I feel, resolve methods are not getting called to validate the values. Do we need to handle anything to make resolve methods work?
In iOS12, we cannot add resolve methods for parameters in custom intents. Resolve methods handled only for specific domains provided within Intents extensions as mentioned in question 1. From iOS13, we can have resolve methods for custom intents based on the parameters.
How can I debug the handler implementation using breakpoint within resolve/handle/confirm methods.
We can add breakpoints and debug intent handler methods.
Thanks.

Related

Updating a WidgetKit widget from a Siri intents extension

I have an interesting quandary I've been grappling with for the past few days: I have an app that also has a widget extension and a Siri intents extension. There is a button in the app that updates a shared data file. Siri intents extension also updates that data file and the widget reflects those changes (to avoid conflicts, I'm using the NSFileCoordinator API).
When I switch to the app, tap the button, and return to the springboard, the widget contents are updated. When I invoke the intent through a shortcut, the app is updated. However, when I invoke the intent, the widget is not updated.
An interesting wrinkle is that widget updates if the app is run in the debug mode from Xcode, which leads me to suspect that this is some sort of a timing issue.
The app, the widget and the intent extension use shared code to read from and to write to the shared data file.
Here is some code:
A. Button in the app:
// …
Button("Update") {
storage.write(updatedData) { (error) in
guard error == nil else { return }
WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
}
}
// …
B. Code in the intents extension
// …
storage.write(updatedData) { (error) in
guard error == nil else {
completion(MyIntentResponse(code: .failure, userActivity: nil))
return
}
WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
completion(MyIntentResponse(code: .success, userActivity: nil))
}
// …
As you can see, I handle the situation when writing the data fails in the intents extesion. If that happens, Siri informs me about it – this is how I know that writing succeeds.
C. Widget entry/timeline code
// …
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (MyEntry) -> ()) {
storage.read { (data, error) in
completion(makeEntryFromData(data))
}
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
storage.read { (data, error) in
let timeline = Timeline(entries: [makeEntryFromData(data)], policy: .never)
completion(timeline)
}
}
// …
Here, although the error state seems not to be handled, in actuality, "data" becomes nil when there is an error, and nil values are handled in the view part of the widget – that way I know that data corruption is not the issue either.
Please guide me, as I'm sorely perplexed!
Cheers,
–Baglan
UPDATE: widget seem to update eventually most of the time. My best guess is, there is some some of a mechanism that throttles updates or schedules them for some time convenient for the OS and that results in unpredictable widget update delays.

TouchID authentication inside Siri Intent Extension

I have an Intent Extension with the View category that is working pretty good for showing an app info.
Now I need to enable TouchID for security reasons, so the user needs to authenticate before requesting the info.
I tried this:
func handle(intent: GetSaldoIntent, completion: #escaping (GetSaldoIntentResponse) -> Void) {
let myContext = LAContext()
myContext.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Unlock to see the info",
reply: { [unowned self] (success, error) -> Void in
if( success ) {
completion(GetSaldoIntentResponse.success(saldo: String(self.paymentProvider.balance)))
return
}
})
completion(GetSaldoIntentResponse(code: .failureRequiringAppLaunch, userActivity: nil))
}
}
But the TouchID dialog closes the Siri screen and then the conversation ends:
Is there a way to request for TouchId validation inside an Intent Extension?
I know PKPayment do something similar, but this isn't a transaction so I can't use ApplePay.
Siri already supports the authorisation, you just need to let Siri know that your intent requires authorisation rather implementing authorisation yourself. Hope that helps you:
https://developer.apple.com/documentation/sirikit/requesting_authorization_to_use_sirikit

siri replay's like "Sorry, there was a problem with the app"

I was just trying to integrating custom siri intent into my app.i have done code for intent handler and i can able to create shortcut but when i run my shortcut. i'm unable to open my app. see this image https://i.stack.imgur.com/m2fby.png
A possible solution is to make sure that your Intent does not take too much memory (~<20Mb) and respond in reasonable time (<5s)
If you missed handling intent then also you might come across this error. Check file IntentHandler.swift
class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling {
override func handler(for intent: INIntent) -> Any {
return MyIntetHandler() //Here, If you are returning self that means you have not handled it.
}
......
Sample intent handler code, the intent name I have created is My, protocol MyIntentHandling is autogenerated you just need to confirm it.
class MyIntetHandler: NSObject, MyIntentHandling
{
func handle(intent: MyIntent, completion: #escaping (MyIntentResponse) -> Void) {
completion(MyIntentResponse(code: .success, userActivity: nil))
}
}

How do I get Siri to create a note?

I am trying to use SiriKit to create a note. Here is my code in the Intents App Extension:
import Intents
class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
// 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.
print("IntentHandler.handler(_:)")
guard let createNoteIntent = intent as? INCreateNoteIntent else {
return self
}
if let textNoteContent = createNoteIntent.content as? INTextNoteContent {
print(textNoteContent.text as Any)
}
return CreateEntry()
}
}
import UIKit
import Intents
class CreateEntry: NSObject, INCreateNoteIntentHandling {
func handle(intent: INCreateNoteIntent, completion: #escaping (INCreateNoteIntentResponse) -> Void) {
print("CreateEntry.handle(_:_:)")
if let textNoteContent = intent.content as? INTextNoteContent {
print(textNoteContent.text as Any)
}
let note = INNote(title: INSpeakableString(spokenPhrase: "May the force be with you."), contents: [INNoteContent()], groupName: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil)
let response = INCreateNoteIntentResponse(code: INCreateNoteIntentResponseCode.ready, userActivity: nil)
response.createdNote = note
completion(response)
}
}
When I ask Siri "Create note using Journal", Siri responds with "Sorry, there was a problem with the app." and my code prints these results.
IntentHandler.handler(_:)
IntentHandler.handler(_:)
IntentHandler.handler(_:)
IntentHandler.handler(_:)
IntentHandler.handler(_:)
CreateEntry.handle(::)
When I change the INCreateNoteIntentResponseCode argument of INCreateNoteIntentResponse to success, Siri responds by saying the note was created and then shows me what the note says, "May the force be with you."
Siri never asked what to say in the note. I thought if I sent a response of ready like in my code above, that Siri would ask the user what to write in the note.
I'm stumped here. There is a lack of documentation for this.
There are optional methods in the Intent Handler that need to be implemented. See https://developer.apple.com/documentation/sirikit/increatenoteintenthandling for details.
The parameters that are passed should be validated in the resolve* methods. The first parameter INCreateNoteIntent has title, content and groupName fields. Depending on whether a parameter is required or optional for your application, you can call the completion callback with the appropriate response. This is partial validation.
confirm is called when all the parameters are set and final validation needs to be done. This is where you can send the .ready status code.
handle is called when the intent needs to be executed, in this intent actual creation of the INNote. Like the other calls, the actual parameters spoken by the user are present in the first parameter.

How to init INStartWorkoutIntent properly in Swift 3?

I know that there's a built-in template for it.
I go to the File menu and choose New > Target
Select iOS > Application extensions from the left-hand pane.
Now choose Intents extension.
That will create two new groups: YourExtension and YourExtensionUI. If you open the YourExtension group you'll see IntentHandler.swift, which contains some sample code for handling workouts.
Here's a much simpler example to get you started:
class IntentHandler: INExtension, INSendMessageIntentHandling {
override func handler(for intent: INIntent) -> AnyObject {
// 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 self
}
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
print("Send message: " + (intent.content ?? "No message"))
let response = INSendMessageIntentResponse(code: .success, userActivity: nil)
completion(response)
}
}
I did that, it's OK.
Now my issue is about using INStart​Workout​Intent instead of INSendMessageIntent, how am I supposed to? Is there a built-in template for this intents too?
Finally, I solved the question by myself.
When you want to use INStartWorkoutIntent properly, you have just to remove all the built-in template content.
You have also to replace INSendMessageIntentHandling by INStartWorkoutIntent Handling.
public func handle(startWorkout intent: INStartWorkoutIntent, completion: #escaping (INStartWorkoutIntentResponse) -> Swift.Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartWorkoutIntent.self))
let response = INStartWorkoutIntentResponse(code: .continueInApp, userActivity: userActivity)
completion(response)
}
DO NOT FORGET:
To your newly created Intents target, fully expand the NSExtension dictionary to study its contents. The dictionary describes in more detail which intents your extension supports and if you want to allow the user to invoke an intent while the device is locked.
Insert the most relevant intents at the top if you want to support more than one. Siri uses this order to figure out which one the user wants to use in case of ambiguity.
We now need to define which intents we want to support.
For example, I want to build an extension that supports the payment intent. Modify the Info.plist file to match the following picture.
Here we specify that we want to handle the INSendPaymentIntent and that we require the device to be unlocked. We don't want strangers to send payments when the device is lost or stolen!
Last thing just to set in target your Intent at the running and it's done.

Resources