Custom Intent Handler Not Called - iOS claims IntentHandlerMethodForIntent not Implemented

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.

Accepted Reply

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.

Replies

First, thanks for providing the detailed walk-through! This is exactly what's needed to help spot the problem.

The issue is that you're mixing a custom intent and a NSUserActivity:

CreatePartsListIntent *data = [[CreatePartsListIntent alloc] init];
data.projectName = [projectPlistName copy];
data.quantity = [NSNumber numberWithInteger : currentProjectQuantity];
[[W_P_rDonationManager sharedInstance] donateCreatePartsListIntent : data];
-(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];
}

Both of these should be the same, and can be either the custom intent (my recommendation), or the NSUserActivity.

Shortcuts backed by a NSUserActivity, which you've created here with the INUIAddVoiceShortcutViewController, will always launch your app to the foreground via the user activity continuation API on either the scene delegate or the app delegate, depending if your app uses scenes or not. This is why you're not seeing the intent handler method ever get called.

Conceptually, it's important to understand the purpose of both the donation and the shortcut you're creating. Donations provide the system with context about common actions users take in your app, which the system learns, and then surfaces opportunistically to the user, even if they haven't set a voice phrase. The donations should accurently reflect the user's actions, taking care that all parameters are accurate and consistent for the same action each time. Any difference in the parameters means the system sees a different interaction; for example, including a "current timestamp" parameter on the donation would mean that each donation looks like a different interaction because the timestamp keeps changing, preventing the system from learning that the user repeats the same action.

Creating the shortcut in your app by using the system's Add to Siri APIs should reflect one of those common actions the user took — the intent with a consistent set of parameter values as described above — and adds an extra way for the user to reach that functionality in your app. If you switch this to using an intent instead of a NSUserActivity, you'll see that your intent handler in the app delegate is called, and your app might not need to come to the foreground to handle the intent, like a user activity does.

When studying the SoupChef sample code project, take a look at the code in Order+Intents.swift. That file contains the method that always turns a soup order into an intent with a consistent parameter set. Working outwards from there, you'll see that the donation path (in SoupOrderDataManager.swift) and creating a shortcut (in AddToSiriCollectionViewCell.swift) all use the same method to create the intent.

  • @edford - Thanks for your reply. You are absolutely correct about the error you found. However, I am so disappointed that I input an out of date implementation in my problem statement. After working through the SoupChef code, I had previously seen error you described and updated my code to remove the line of code you mentioned where an NSUserActivity is used to create a parts list shortcut. Then I replaced the INShortcut *shortcut line with the following:

    CreatePartsListIntent *intentWithData = [[WoodPickerDonationManager sharedInstance] prepareCreatePartsListIntent : @"" withNumberOfAssemblies : 1]; INShortcut *shortcut = [[INShortcut alloc] initWithIntent:intentWithData];

    The call to prepareCreatePartsListIntent does the following:

         `-(CreatePartsListIntent *) prepareCreatePartsListIntent : (NSString *) partsListName withNumberOfAssemblies : (NSInteger) quantity {         CreatePartsListIntent *intentWithData = [[CreatePartsListIntent alloc] init]; intentWithData.projectName = partsListName; intentWithData.quantity = [NSNumber numberWithInteger: quantity]; intentWithData.suggestedInvocationPhrase = @"Create Parts List";

    return intentWithData; }`

    Sadly, even with this change, the intentHandler in my Intents Extension is not called. In your answer, you indicated that if I switch my implementation to using an intent, that my intentHandler in the app delegate will be called. That confuses me. Is there supposed to be an intentHandler in the app delegate in addition to the Intents Extension? Is there any thing else you see that might be wrong?

Add a Comment

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.