The IntentHandler in my objective-C implementation of a custom intent fails to receive a call from a voice activated shortcut. When using Siri to invoke the donated interaction, I have observed that I receive the following errors in the Console app that claim the intent handler method for intent is unimplemented:
-[INIntentDeliverer _invokeIntentHandlerMethodForIntent:intentHandler:parameterNamed:keyForSelectors:executionHandler:unimplementedHandler:] _invokeIntentHandlerMethodForIntent sirikit.intent.voice_commands.RunVoiceCommandIntent
-[WFRVCIntentHandler stateMachineForIntent:] Created state machine <WFRVCStateMachine: 0x102e23970 state=WaitingForServer phase=Unknown> for intent with identifier 8A87FC68-329D-49FF-B534-B0A5821854CA
-[INIntentDeliverer _invokeIntentHandlerMethodForIntent:intentHandler:parameterNamed:keyForSelectors:executionHandler:unimplementedHandler:] _invokeIntentHandlerMethodForIntent sirikit.intent.voice_commands.RunVoiceCommandIntent
This error is consistent with the fact that an attempt to trigger the custom intent with a voice command results in iOS calling my appDelegate, and in particular the application:continueUserActivity:restorationHandler:. According to the documentation, the restorationHandler should only be called if the intent is not handled and must be handled by the main app.
As there is very little documentation for an objective-C implementation, I cannot figure out what I am missing. I have tried to map the sample SoupChef app implementation of Siri Shortcuts to my implementation. I cannot figure out where I am going wrong. Here is my implementation (sorry for all the details, but I am hoping you can see something wrong):
First, I have implemented two additional targets; a Shared Framework and an Intents Extension. I have also implemented an Intents Definition File. Here is an image of my targets:
W_P_r is the main app, W_P_rKit is the shared framework, and PartsListManagerIntents is the Intents Extension. Next, here is my Intents Definition file and the target membership that it belongs to:
I have also added an app group to the capabilities section of the add for both the main target and the PartsListIntentManager target. And I added Siri capability to the main target. All of this auto-creates some code, including a default IntentHandler.m and an info.plist in the PartsListManagerIntents target. I have updated the info.plist as follows:
And here is the Auto-generated IntentHandler (which I have modified to log activity and to call a specific intent handler that resides in the W_P_rKit shared framework:
#import "IntentHandler.h"
#import <Intents/Intents.h>
#import <W_P_rKit/W_P_rKit.h>
#import "CreatePartsListIntentHandler.h"
#import "P__tHandler.h"
#import <os/log.h>
@interface IntentHandler () /* <CreatePartsListIntentHandling, P__tHandling> */
@end
@implementation IntentHandler
- (id)handlerForIntent:(INIntent *)intent {
os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "handlerForIntent: Reached IntentHandler.");
if ([intent.identifier isEqualToString:@"P__rIntent"]) {
NSLog(@"P__rIntent");
return [[P__rIntentHandler alloc] init];
}
else if ([intent.identifier isEqualToString:@"CreatePartsListIntent"]) {
NSLog(@"CreatePartsListIntent");
os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "handlerForIntent: IntentHandler Received CreatePartsListIntent.");
return [[CreatePartsListIntentHandler alloc] init];
}
return self;
}
Note that CreatePartsListIntentHandler is a class that implements the CreatePartsListIntentHandling protocol (resolve, confirm, and handle methods of the IntentHandler). Now here is the relevant implementation that should trigger iOS to call the IntentHandler:
In my app at the point where the user fills in the name of a new project I make a call to donate the interaction as follows:
CreatePartsListIntent *data = [[CreatePartsListIntent alloc] init];
data.projectName = [projectPlistName copy];
data.quantity = [NSNumber numberWithInteger : currentProjectQuantity];
[[W_P_rDonationManager sharedInstance] donateCreatePartsListIntent : data];
The call to donateCreatePartsListIntent does the following:
data.suggestedInvocationPhrase = @"Create Parts List";
INInteraction* interaction = [[INInteraction alloc] initWithIntent:data response:nil];
[interaction donateInteractionWithCompletion:^(NSError * _Nullable error) { ... }
Once the user has created the empty parts list (forcing the above interaction donation to occur), the view controller will present an "Add Siri Shortcut" button. The tapping of the button automatically calls the following method to create a shortcut:
-(void) addCreatePartsListShortcutWasTapped {
NSUserActivity *userActivity = [[W_P_rDonationManager sharedInstance] CreatePartsListShortcut];
INShortcut *shortcut = [[INShortcut alloc] initWithUserActivity:userActivity];
INUIAddVoiceShortcutViewController *addSiri = [[INUIAddVoiceShortcutViewController alloc] initWithShortcut:shortcut];
addSiri.delegate = self;
[self presentViewController:addSiri animated:YES completion: nil];
}
The call to CreatePartsListShortcut does the following:
NSUserActivity *newActivity = [[NSUserActivity alloc] initWithActivityType: kW_P_rCreatePartsListActivityType];
newActivity.persistentIdentifier = kW_P_rCreatePartsListActivityType;
newActivity.eligibleForSearch = TRUE;
newActivity.eligibleForPrediction = TRUE;
CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithContentType: UTTypeImage];
newActivity.title = @"Create Parts List";
attributeSet.contentDescription = @"Create a parts list for a new project";
newActivity.suggestedInvocationPhrase = @"Create Parts List";
UIImage *image = [UIImage imageNamed:@"W_P_r_Icon.jpg"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;
newActivity.contentAttributeSet = attributeSet;
return newActivity;
This does create a shortcut that is visible in the shortcuts app. Clicking on the shortcut or saying the invocation phrase will take you directly to the the app delegate's application:userActivity:restorationHandler. But it does not call the IntentHandler. I know this because I have implemented logs that would tell me if the execution thread came there.
Why is my IntentHandler not being called? Why is iOS sending the error message _invokeIntentHandlerMethodForIntent:intentHandler:parameterNamed:keyForSelectors:executionHandler:unimplementedHandler:?
I have actually been struggling with this for weeks. Any help or hints would be so helpful.
After months of frustration, I finally got this working. I had to create a new app with custom intents implemented. I cut and pasted all the relevant code listed above, and low and behold, it worked. Then I painstakingly went through every setting until I found the problem.
There is a checkbox under Build Phases/Embed App Extensions (selected my project in the project navigator and then selected my App target). This checkbox "Copy only when installing" was checked. After unchecking this, everything works. Since I don't know what this is, I must have checked it out of desperation after seeing this as a solution to someone's post on intents.
Also, it turns out that the errors I mentioned in the problem statement above are not errors of any consequence. They occur regardless.