0%

让App在iOS12中支持Siri Shortcuts(捷径)功能 和 “设置和捷径App内未列出自己App的Shortcut” 的解决办法

最近发现的问题 “设置和捷径App内未列出自己App的Shortcut” 的解决办法在文章末尾

背景介绍

WWDC 2018 苹果更新了Siri使其支持Shortcuts功能,中文名“捷径”。支持用户通过自定义把一系列操作合并到一个Shortcut内。苹果官方应用提供了一些接口供Shortcut调用,如Map应用的“获取行程时间”,“显示路线”等。如果我们的应用需要支持,需要自己在App中开放可以被Shortcuts访问的接口。同时苹果正在大力推广这个Siri新功能,支持软件可在苹果App Store榜单“用 Siri,走捷径”中列出,有一定推广作用。该功能也有利于增加用户粘度和活跃人数。

Apple提供了官方关于Shortcuts的demo,但是在苹果大力推广Swift的浪潮下,demo的语言也是Swift版的,无奈公司现在还在使用Objc,下面介绍下Objc下的接入过程。

创建Custom Intent

在项目中通过“New File…”创建一个Intents.intentdefinition文件。这个文件用来定义自定义intent类型。

同时会自动在项目的Info.plist文件中添加NSUserActivityTypes

添加Frameworks

在项目的Build Phases中的Link Binary With Libraries中添加Intents.frameworkIntentsUI.framework

添加Shortcut按钮

在项目中需要调用添加Shortcut按钮的.m文件中,

增加import

1
2
3
#import <Intents/Intents.h>
#import <IntentsUI/IntentsUI.h>
#import "HWTakePhotoIntent.h" //上方“设置Custom Intents”图中右边箭头指的“Class Name”

添加Delegate

1
2
3
<INUIAddVoiceShortcutButtonDelegate,
INUIAddVoiceShortcutViewControllerDelegate,
INUIEditVoiceShortcutViewControllerDelegate>

添加Property

1
2
@property (nonatomic, strong) HWTakePhotoIntent API_AVAILABLE(ios(12.0)) *intent;
@property (nonatomic, strong) INUIAddVoiceShortcutButton API_AVAILABLE(ios(12.0)) *shortcutButton;

添加shortcutButton按钮

1
2
3
4
5
6
7
if (@available(iOS 12.0, *)) {
_shortcutButton = [[INUIAddVoiceShortcutButton alloc] initWithStyle:INUIAddVoiceShortcutButtonStyleWhiteOutline];
_shortcutButton.shortcut = [[INShortcut alloc] initWithIntent:self.intent];
_shortcutButton.translatesAutoresizingMaskIntoConstraints = false;
_shortcutButton.delegate = self;
[self.view addSubview:_shortcutButton];
}

设置intent属性

1
2
3
4
5
6
7
- (HWTakePhotoIntent *)intent API_AVAILABLE(ios(12.0)){
if (!_intent) {
_intent = [[HWTakePhotoIntent alloc] init];
_intent.suggestedInvocationPhrase = @"开始改作业"; //在Siri语音设置时显示的建议设置唤起文字
}
return _intent;
}

设置对应delegate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#pragma mark - INUIAddVoiceShortcutButtonDelegate
- (void)presentAddVoiceShortcutViewController:(INUIAddVoiceShortcutViewController *)addVoiceShortcutViewController forAddVoiceShortcutButton:(INUIAddVoiceShortcutButton *)addVoiceShortcutButton API_AVAILABLE(ios(12.0)){
addVoiceShortcutViewController.delegate = self;
[self presentViewController:addVoiceShortcutViewController animated:YES completion:nil];
}

- (void)presentEditVoiceShortcutViewController:(INUIEditVoiceShortcutViewController *)editVoiceShortcutViewController forAddVoiceShortcutButton:(INUIAddVoiceShortcutButton *)addVoiceShortcutButton API_AVAILABLE(ios(12.0)){
editVoiceShortcutViewController.delegate = self;
[self presentViewController:editVoiceShortcutViewController animated:YES completion:nil];
}

#pragma mark - INUIAddVoiceShortcutViewControllerDelegate
- (void)addVoiceShortcutViewController:(INUIAddVoiceShortcutViewController *)controller didFinishWithVoiceShortcut:(INVoiceShortcut *)voiceShortcut error:(NSError *)error
API_AVAILABLE(ios(12.0)){
[controller dismissViewControllerAnimated:YES completion:nil];
}

- (void)addVoiceShortcutViewControllerDidCancel:(INUIAddVoiceShortcutViewController *)controller API_AVAILABLE(ios(12.0)){
[controller dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - INUIEditVoiceShortcutViewControllerDelegate
- (void)editVoiceShortcutViewControllerDidCancel:(INUIEditVoiceShortcutViewController *)controller API_AVAILABLE(ios(12.0)){
[controller dismissViewControllerAnimated:YES completion:nil];
}

- (void)editVoiceShortcutViewController:(INUIEditVoiceShortcutViewController *)controller didUpdateVoiceShortcut:(INVoiceShortcut *)voiceShortcut error:(NSError *)error API_AVAILABLE(ios(12.0)){
[controller dismissViewControllerAnimated:YES completion:nil];
}

- (void)editVoiceShortcutViewController:(INUIEditVoiceShortcutViewController *)controller didDeleteVoiceShortcutWithIdentifier:(NSUUID *)deletedVoiceShortcutIdentifier API_AVAILABLE(ios(12.0)){
[controller dismissViewControllerAnimated:YES completion:nil];
}

AppDelegate.m文件中添加方法

1
2
3
4
5
6
7
8
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
// activityType 和 Info.plist中的NSUserActivityTypes内容对应
if ([userActivity.activityType isEqualToString:@"HWTakePhotoIntent"]) {
[HWNotificationCenter postNotificationName:kNotificationOpenCamera object:nil]; // 通过通知找到对应class处理activity
}
return YES;
}

用于在Siri中通过设定语音调起应用时处理Siri的请求。我们的项目中是打开摄像头拍照批改作业。

iOS12 Siri Known Issues

如上图红框所示,iOS12发布后已知问题之一是系统的INUIAddVoiceShortcutButton只支持默认的标题“Add to Siri”和“Added to Siri”,没有做本地化支持😂。此外按钮的样式也很死,无法自由自在的自定义,所以灰溜溜的只能自己来写自定义按钮了。

需求一:判断是否添加过该shortcut来区别跳转INUIAddVoiceShortcutViewController还是INUIEditVoiceShortcutViewController

当然苹果为我们提供了INVoiceShortcutCenter- (void)getAllVoiceShortcutsWithCompletion:(void(^)(NSArray<INVoiceShortcut *> * _Nullable voiceShortcuts, NSError * _Nullable error))completionHandler;方法来获得所有添加的Shortcuts或者- (void)getVoiceShortcutWithIdentifier:(NSUUID *)identifier completion:(void(^)(INVoiceShortcut * _Nullable voiceShortcut, NSError * _Nullable error))completionHandler NS_SWIFT_NAME(getVoiceShortcut(with:completion:));通过identifier查找对应的Shortcut。

我在项目中的按钮点击事件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)shortcutButtonClicked:(UIButton *)sender
{
if (@available(iOS 12.0, *)) {
[[INVoiceShortcutCenter sharedCenter] getAllVoiceShortcutsWithCompletion:^(NSArray<INVoiceShortcut *> * _Nullable voiceShortcuts, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL tempAddedShortcut = NO;
for (INVoiceShortcut *voiceShortcut in voiceShortcuts) {
if ([voiceShortcut.shortcut.intent isKindOfClass:[HWTakePhotoIntent class]]) {
tempAddedShortcut = YES;
break;
}
}
self.addedShortcut = tempAddedShortcut;
if (self.addedShortcut) {
INUIEditVoiceShortcutViewController *editVoiceShortcutViewController = [[INUIEditVoiceShortcutViewController alloc] initWithVoiceShortcut:voiceShortcuts[0]];
editVoiceShortcutViewController.delegate = self;
[self presentViewController:editVoiceShortcutViewController animated:YES completion:nil];
} else {
INShortcut *shortcut = [[INShortcut alloc] initWithIntent:self.intent];
INUIAddVoiceShortcutViewController *addVoiceShortcutViewController = [[INUIAddVoiceShortcutViewController alloc] initWithShortcut:shortcut];
addVoiceShortcutViewController.delegate = self;
[self presentViewController:addVoiceShortcutViewController animated:YES completion:nil];
}
});
}];
}
}

需求二:通过INUIAddVoiceShortcutViewControllerINUIEditVoiceShortcutViewController的Delegates更新自定义Shortcut按钮状态

同样也需要用到需求一中提到的判断是否添加过当前Shortcut来更新对应的自定义Shortcut按钮样式。

“设置和捷径App内未列出自己App的Shortcut” 的解决办法

问题描述

按照上文介绍,一步步在自己的App中添加Shortcut功能成功后,我们并不能在系统的设置应用的Siri 与 搜索中看到我们刚刚添加的Shortcut,或者在捷径应用中找到。如下图:

如果我们通过上文中定义的App内的Add to Siri按钮添加用户的Shortcut到Siri成功后,我们依然可以在设置捷径应用中看到。但是如果删除添加的Shortcut,有都会消失。这显然不是我们需要的效果。

预期效果是不管用户是否在App内添加Shortcut到Siri,都应在设置捷径应用中看到。

解决办法

我们需要自己手动更新我们应用的Shortcut Suggestions的内容,这样才能在设置捷径应用中列出。

更新Shortcut Suggestions内容的代码如下。

1
2
3
4
5
6
7
8
- (void)addMenuItemShortcuts
{
if (AVAILABLE(12.0)) {
HWTakePhotoIntent *intent = [[HWTakePhotoIntent alloc] init];
intent.suggestedInvocationPhrase = NSLocalizedString(@"SIRI_SHORTCUT_CORRECT_WORK", nil);
[[INVoiceShortcutCenter sharedCenter] setShortcutSuggestions:@[[[INShortcut alloc] initWithIntent:intent]]];
}
}

我是在AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions里做了初始化。

更新Shortcut Suggestions内容后,即使不在应用内添加Shortcut到Siri,在设置捷径应用中仍能找到应用提供的Shortcut建议。

设置中的界面如下

点击捷径进入后如下

如果你需要,也可以在其他需要的时候更新Shortcut Suggestions内容。

总结

Siri Shortcut大功告成

References:

iOS12 Siri Shortcuts初探

Human Interface Guidelines - SiriKit

Documentation - SiriKit