SiriKit intent always times out on first run after build - ios

After I first build and run my intents project, requesting something from Siri always returns with Sorry, you will need to continue in the app
However, it works every time after that until I rebuild.
I put breakpoints in handler:
override func handler(for intent: INIntent) -> Any {
return self
}
and in handle:
func handle(requestRide intent: INRequestRideIntent, completion: #escaping (INRequestRideIntentResponse) -> Void) {
...
}
The breakpoint in handler is hit a few seconds after Siri says continue in app.

SiriKit is very picky on waiting. Your first run will time out because of the time it takes Xcode to attach the debugger to Siri's process.
I've just learned to live with it.

Related

Create custom "read" Intent like GetCurrentLocation

I'm creating shortcuts for a communication app. In this app there is a presence state (available, dnd, ...). I implemented an intent to set said presence state which worked fine. I created a "read" intent which just returns the currently set state, which works, but i can't pass the result to the next action.
For simplification and testing i created another intent just return a string, trying to process it, but i'm still unable to do that. My intent has no input parameters.
What am i missing? My Goal would be an interaction like Get Current Location offers.
class Return5IntentHandler: NSObject, Return5IntentHandling {
func handle(intent: Return5Intent, completion: #escaping (Return5IntentResponse) -> Void) {
completion(.success(result: "5"))
}
}
Classic case of searching for hours, writing a question only to find the answer 30min later yourself...
One has to explicitly set the output, which is the passed on value for following actions.

SiriKit: How to set output property when intent is handled in the main app?

Context
I'm working on adding support for Siri Shortcuts (Intents?) to a non-trivial app. My main goal is to allow users to automatise some tasks within the app using Shortcuts.app.
I have defined MyIntent in the Intents.intentdefinition in the Intents Extension target, together with MyIntentResponse, which has an output property file: INFile.
Given the non-trivial nature of the app, I'm forwarding the intent from Intent Extension to the main app:
func handle(intent: MyIntent, completion: #escaping (MyIntentResponse) -> Void) {
// Can't handle the intent in the app extension yet, let the main app handle it
let response = MyIntentResponse(code: .continueInApp, userActivity: nil)
// TODO: How to update the response (with an output property) if the intent continues in the app?
completion(response)
}
Problem
Once the intent is forwarded to the app, the AppDelegate.application(_:continue:restorationHandler:) method is called with a user activity which also has the property interaction: INInteraction? set.
From the provided INInteraction I can get both the intent: INIntent and response: INIntentResponse to handle the intent accordingly.
What's missing, however, is a way how to communicate back to the Shortcuts.app about the result of the intent and provide the desired output property (file: INFile).
Question
Is there a way how to provide an intent response with an output property set if the intent is handled in the main app?
Details
Interestingly, there's a AppDelegate method that should handle this use case:
optional func application(_ application: UIApplication,
handle intent: INIntent,
completionHandler: #escaping (INIntentResponse) -> Void)
And the documentation says:
An app with an Intents app extension can use this method to handle an intent directly, instead of handling it in the app extension. You might use this method to implement workflows that you cannot implement easily in your extension. For example, you might use it to start or manage a user's workout session. If your app is not running, SiriKit launches your app in the background so that the Siri interface remains active.
More info: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/2887573-application
In theory, it should be possible just to call the completionHandler with a newly instantiated MyIntentResponse which would have file property set correctly.
However, this method is never called. Instead, the above mentioned AppDelegate.application(_:continue:restorationHandler:) is called.
If you use .handleInApp instead of .continueInApp in your Intents extension handle() function, then your app delegate's handleIntent() function will be called instead of the continue:restorationHandler() function. This will allow you to pass back INIntentResponse back to the extension in the completionHandler.

SiriKit Intent Handler not being called

I have an app that incorporated Siri Intents and they work well. You can have Siri launch the app and it will perform the action as expected. All of my intents need to happen in the app so I do not have an extension for them.
However using the Shortcuts app, when I use my shortcut the app will just stop in my app and not continue so Siri is not receiving the handler response.
Right now my delegate will open from an action inside of the NSUserActivity call and I will use a custom class to determine which Shortcut it is and perform said action.
I do have that custom class conforming to the IntentHandler Protocol for each action and I call a .success response in the completion handler.
Heres where I think I may be mistaken. I manually call the Handler protocol inside of my custom class.
func handleSiri(_ intent: INItent) {
if intent is ActionIntent {
func handle(intent: ActionIntent, completion: #escaping (ActionIntent Response) -> Void) {
let response = DisconnectIntentResponse.init(code: .success, userActivity: nil)
print("Intent was a success")
completion(response)
}
}
}
However Siri never completes. Should I be calling this manually? Do I need to have an extension call these functions?
The only reason why this is not called is because you have not added the Voice Intent extension into your dependencies list under Build Phase section for your App Target.

What happens when you return `false` from `application(_:, continue:​, restoration​Handler:​)`?

I have enabled Universal Links in my app. The corresponding delegate call to handle those links is
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if canHandle(userAcitivity) {
// Handle the universal link.
}
else {
// ⛔️ Don't handle the universal link.
return false
}
}
No I wonder what exactly happens when I return false from this method. In the beginning I thought that Safari would simply open the link instead as it would without universal links enabled. However, I figured that my app is still opened and the documentation states:
If you do not implement this method or if your implementation returns false, iOS tries to create a document for your app to open using a URL.
What exactly does this mean?
What kind of document is created and how is my app notified about that?
Actually, for continueUserActivity the description of the return value is:
Returns true to indicate that your app handled the activity or false to let iOS know that your app did not handle the activity.
If you return YES from this function the OS will understand that no further processing of the NSUserActivity will be required - your app has done everything that needs to be done. If you return NO from this function, the OS will understand that an activity requiring OS processing may have occurred and may need to be handled.
You can get more background on all this in Apple's Handoff docs, here: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html#//apple_ref/doc/uid/TP40014338

Handling system alerts in Xcode UI tests

How do you test for a system alert in an Xcode UI test, for example when accessing Itunes from an app.
You need to first use a UI interruption handler, as described here.
addUIInterruptionMonitorWithDescription("Sign In") { (alert) -> Bool in
alert.buttons["Cancel"].tap()
return true
}
app.otherElements[" Upgrade"].tap()
app.tap()

Resources