3

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.

JeffB6688
  • 3,782
  • 5
  • 38
  • 58
  • I documented a lot when I built my implementation. Maybe this will help? https://pqvst.com/2018/09/21/integrating-ios-12-siri-shortcuts-using-objective-c/ – pqvst Dec 15 '21 at 03:10
  • @pqvst Yes, it was a good article and that is the reason I contacted you. I was wondering about one thing you said in that article. Could you elaborate on what problem you were experiencing when you wrote: 3) Add the Intents Definition File to your Intents Extension target. Note: I tried moving the intents definition file to a shared Cocoa Touch Framework that both the main app and the intents extension had access to, however that does not seem to work. So, I recommend leaving the intent definition file in the main app target and just adding the intents target to the target membership setting. – JeffB6688 Dec 16 '21 at 00:14
  • Tbh, I don't really remember as it's been a couple of years now, but I think I got compilation errors if the Intents Definition File wasn't located in the main app. In general, I remember having a lot of issues. Generally I found that removing all saved shortcuts, reinstalling the app, and starting from scratch helped in a lot of cases. – pqvst Dec 16 '21 at 17:07
  • When you get the call to `application:continueUserActivity:restorationHandler:` is the `activity.interaction.intent` populated? – davidgyoung Dec 21 '21 at 03:41
  • @davidgyoung In the Xcode console, the interaction.intent parameter autocompletes (i.e. it shows that the parameter exists. But when I try to print this, I get the following error: error: :1:14: property 'interaction' not found on object of type 'NSUserActivity *' userActivity.interaction.intent. Why do you think that is the case? If I print userActivity.activityType, it prints my intent "CreatePartsListIntent", so it knows about my intents. – JeffB6688 Dec 22 '21 at 16:36
  • It's unclear if the error in lldb console is a debugger issue or a problem with the code setup. You might try adding a new NSLog statement to your code that tries to print out the intent (or assign it to a local variable), so you can inspect it in the console after a breakpoint. This would eliminate any debugger funniness. – davidgyoung Dec 22 '21 at 16:43

3 Answers3

1

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.

JeffB6688
  • 3,782
  • 5
  • 38
  • 58
0

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.

davidgyoung
  • 63,876
  • 14
  • 121
  • 204
0

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!

davidgyoung
  • 63,876
  • 14
  • 121
  • 204
  • I had hoped that this might have solved my problem. While my targets all had the same minimum iOS version, my PROJECT iOS Deployment Target was different. So I updated everything to version 14.5. Unfortunately, this did not fix the problem. Thanks for trying to help me though. – JeffB6688 Jan 04 '22 at 18:14
  • One thing that I noticed is that when I attempt to put a breakpoint in my autogenerated IntentHandler, the breakpoint is silhouetted and if you hover over it, it says: Xcode won’t pause as this breakpoint because it has not been resolved. Resolving it requires that: -The compiler generates a location for the expression of the column breakpoint. -It is valid to have no such locations. -The line at the breakpoint is compiled. -The compiler generates debug information that is not stripped out (check the Build Settings). -The library for the breakpoint is loaded. Is this a problem? – JeffB6688 Jan 04 '22 at 18:17
  • For the breakpoints in IntentHandlers to be resolved properly, you can't run your main target from Xcode, you have to run your App Intent Extension target, at which point Xcode will prompt you for the companion app to run with it, and you then select your main app. See here: https://stackoverflow.com/questions/38093871/how-do-i-debug-my-siri-intents-extension – davidgyoung Jan 05 '22 at 13:11