Custom Intent Handler Not Called - iOS claims IntentHandlerMethodForIntent not Implemented

1.6k views Asked by At

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:

enter image description here

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:

enter image description here

enter image description here

enter image description here

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:

enter image description here

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 {
    CreatePartsListIntent *intentWithData = [[WoodPickerDonationManager sharedInstance] prepareCreatePartsListIntent :
                                         @"" withNumberOfAssemblies : 1];
    INShortcut *shortcut = [[INShortcut alloc] initWithIntent:intentWithData];
    INUIAddVoiceShortcutViewController *addSiri = [[INUIAddVoiceShortcutViewController alloc] initWithShortcut:shortcut];
    addSiri.delegate = self;    
    [self presentViewController:addSiri animated:YES completion: nil];
}

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;
}

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.

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.

3

There are 3 answers

0
JeffB6688 On BEST ANSWER

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.

0
davidgyoung On

I suspect that the Target Membership selection of your .intentdefinition file is too restrictive. I believe you should set it to "Public Intent Classes" for all three targets (the screenshot shows "No Generated Classes" for two of them.

I'm not certain this is the cause of the problem, but it is worth trying to see if the problem or console log messages go away.

I will note that I am experiencing the same symptoms as you with a mixed Objective C / Swift project. The AppDelegate is in Objective C and most other code is in Swift. I also see that the AppDelegate method gets called, but the intent handler does not. Although in my case, I have all targets set to "Public Intent Classes" and Console does not show the same log lines you describe. I suspect if you make the change describe above, it might make these log lines go away without solving the core problem -- if so, this would indicate those log lines are a red herring.

A few other notes about my case that may be similar or different than yours: (1) I am using an old Xcode project with the "Legacy Build System" where I have recently tried to add Siri support. (2) I am building with Xcode 13.1 but testing on an older iOS 12.4.6 device because that is the minimum OS version of our users. I have not tried on a newer iOS version.

EDIT: See my other answer for the resolution to my problem. I got this working with a Legacy Build System on Xcode 13.1 on an older iOS 12.4.6 device.

3
davidgyoung On

Check that the XCode Build Settings for all your targets (Siri Intent target, IntentUI target, etc.) all specify the same minimum iOS version under Deployment Info as shown here:

enter image description here

If the Intent target specifies a higher minimum iOS version than the device you are using to test, then the IntentHandler will never get called and the AppDelegate method in your main target will get called instead. In my case, as soon as I changed the Deployment Info to a lower iOS version, the IntentHandler immediately started getting called and the AppDelegate method no longer was called.

Credit to Ondřej Korol for this idea: https://stackoverflow.com/a/59500997/1461050

This problem is particularly likely to happen when modifying old projects for older iOS versions, because when you create a new target in XCode it defaults the DeploymentInfo to the default for XCode even if it does not match the DeploymentInfo of your main target. Unfortunately, this is one of the many issues that makes SiriKit development super fragile. One little thing wrong and the whole thing fails silently!