How to init INStartWorkoutIntent properly in Swift 3? - ios

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.

Related

iOS siri shortcuts Swift

I am learning swift currently for our radio station, and I have noticed that a rival station as been able to set up a siri shortcut that allows people to say "Hey Siri play (station name)" and Siri will open the app and start playing said station.
I am wondering how that could be done with Swift?
You can do it with the help of Siri's intent. You will have to create a Siri intent definition file using Xcode.
Sample Intent Definition for PlayGame:
In the above example, I have created simple intent for play games. I have added category as Do, There are many categories you can choose from. You can also pass the parameter through intent, just like I have used game name you can add radio station name there.
You can also add this particular intent for the Siri suggestion feature.
Handling Siri Intent event:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == "PlayGameIntent" {
print(userActivity.userInfo ?? "")
}
return true
}
Note: You will have to donate this intent to the iOS to handle, you
can do it by following way:
let intent = PlayGameIntent()
intent.gameName = "PUBG"
let interaction = INInteraction(intent: intent, response: nil)
interaction.donate { (error) in
print(error ?? "error")
}

Facing issue in Siri Integration with custom intents

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.

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 do I exactly use MMWormhole with Swift?

I have an iPhone application and added a WatchKitExtension. From the iPhone App I want to pass a String to the WatchApp which is supposed to change an image on the Watch.
What I already did was to download the source files and import the MMWormhole.m & .h. They are written in Obj-C and so Xcode automatically bridged them for me.
I also added an app group and activated it for my WatchExtension & my iPhone target
In the tutorial on GitHub it says I have to initialize the wormhole with:
self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:#"group.com.mutualmobile.wormhole"
optionalDirectory:#"wormhole"];
...and send a message using:
[self.wormhole passMessageObject:#{#"titleString" : title}
identifier:#"messageIdentifier"];
But I have actually no idea where to put that, I am using Swift in my iPhone application and the WatchExtension.
Can anyone please help me there?
I suppose it depends on different applications, but for one application I put the listeners in the didFinishLaunchingWithOptions method of my app delegate in the main iOS app. This was because the user would be using the watch, and they would be relaying information off to the phone
There were multiple listeners...
var wormhole = MMWormhole(applicationGroupIdentifier: "group", optionalDirectory: nil)
wormhole.listenForMessageWithIdentifier("identifier", listener: { (message) -> Void in
//do stuff
})
wormhole.listenForMessageWithIdentifier("identifier2", listener: { (message) -> Void in
//do stuff
})
wormhole.listenForMessageWithIdentifier("identifier3", listener: { (message) -> Void in
//do stuff
})
And then in a WKInterfaceController, I sent a message. Sometimes in an action, sometimes in the willActivate method. It really depends on the flow of your app
var wormhole = MMWormhole(applicationGroupIdentifier: "group", optionalDirectory: nil)
#IBAction func buttonPushed(){
wormhole.passMessageObject("object", identifier: "identifier1")
}
This can work both ways though, I could have very easily put a listener in my watch which would wait for messages initiated by some Interface Controller on the phone.
Here are my instructions. Hopefully they help you with the simplest use case and then you can expand from there. (Remember to structure your code so that it actually makes sense!)
Get MMWormhole (the .h and the .m) added to your project. If you know how to use Cocoapods, do that, but otherwise, just use git submodules. (I use git submmodules)
Because you need the .h to be visible from Swift, you need to use a bridging header.
Set up an App Group, which requires using the Developer Portal. Link is here
In your iPhone build target -> Capabilities -> App Groups and add your group. If all three checkboxes do not go perfectly, go back to the Developer Portal and make sure everything is right or start again.
MMWormhole, iPhone Side
Set up the wormhole somewhere you can reach it. NOTE: your group ID has to be the one from above!
let wormhole = MMWormhole(applicationGroupIdentifier: "group.testMe.now", optionalDirectory: nil)
wormhole.listenForMessageWithIdentifier("wormholeMessageFromWatch", listener: { (message ) -> Void in
if let messageFromWatch = message as? String {
// do something with messageFromWatch
}
})
iPhone App Sends String
wormhole.passMessageObject("message from phone to watch", identifier: "wormholeMessageFromPhone")
iPhone app registers to receive and sends again in the callback via MMWormhole (asynchronous but cool)
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
universe.initWormhole(.phone, messageHandler: { (message) -> () in
universe.wormhole.passMessageObject("the phone got \(message)", identifier: "wormholeMessageFromPhone")
})
return true
}
MMWormhole, Apple Watch Side
Set up the wormhole somewhere you can reach it. NOTE: your group ID has to be the one from above!
let wormhole = MMWormhole(applicationGroupIdentifier: "group.testMe.now", optionalDirectory: nil)
wormhole.listenForMessageWithIdentifier("wormholeMessageFromPhone", listener: { (message ) -> Void in
if let messageFromPhone = message as? String {
// do something with messageFromPhone
}
})
MMWormhole, watch app registers to receive
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
universe.initWormhole(.watch, messageHandler: { (message) -> () in
println("MMWormhole Message Came to Watch: \(message)")
})
}
MMWormhole, watch app sends
// force open the parent application because otherwise the message goes nowhere until the app is opened
WKInterfaceController.openParentApplication(["":""], reply: nil)
universe.wormhole.passMessageObject("[from watch to phone]", identifier: "wormholeMessageFromWatch")

Resources