最近发现的问题 “设置和捷径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.framework
和IntentsUI.framework
。
添加Shortcut按钮
在项目中需要调用添加Shortcut按钮的.m文件中,
增加import
1 2 3
| #import <Intents/Intents.h> #import <IntentsUI/IntentsUI.h> #import "HWTakePhotoIntent.h"
|
添加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;
|
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 = @"开始改作业"; } 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 { if ([userActivity.activityType isEqualToString:@"HWTakePhotoIntent"]) { [HWNotificationCenter postNotificationName:kNotificationOpenCamera object:nil]; } 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]; } }); }]; } }
|
需求二:通过INUIAddVoiceShortcutViewController
和INUIEditVoiceShortcutViewController
的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