iOS12 Siri ShortCuts 应用 通过Intents方式实现【转】

我的demo(内含详细注解):https://github.com/huchuankan/IOSDemo

ios14补充,ios的Siri呼唤出来从全屏变成了 home键上面的彩色圈圈,这种模式下好像无法用快捷命令的短语打开界面, 我的方式是 设置--siri 里面打开“键入以使用Siri”按钮, 这样在呼唤 ”嘿 siri“的时候,还是全屏的siri模式,这时候说出短语,就能实现功能, 个人琢磨不知道为啥,也不知道有没有其他办法

原文有一点小坑,下文为我demo后的改进,可对比阅读

今天这篇文章就来介绍另外一种功能,通过 Intents Extension 实现不打开app 去完成某个任务。先来看下效果:

SiriKit Intent Definition File

在新建Extension之前,我们要通过 file->newfile, 选择 SiriKit Intent Definition File。创建好后是这个样子的:

上图中,我一共自定义了三个 Intent, 分别是 DailyPunch、BreakfastPunch和 SportPunch。在这个界面,你可以设置 Intent 的 category,title和一些参数。每个 Intent 都对应一个 Response, 如下图所示。你可以在response定义参数和错误类型。如图所示,我定义了 errorMessage 的参数 和 failureUnLogin等两个错误类型。这里 errorMessage 用来传递服务器返回的错误信息。

注意,需要交互按钮的就勾选 User confirmation required

创建完成后,编译一下项目,xcode 会自动生成对应的类,我这里的话会生成 DailyPunchIntent 等三个类,每个类包含了 DailyPunchIntentHandling 协议和 DailyPunchIntentResponse 类等所需要的内容。

需要注意的是,这些类不会出现在项目的目录中,有点和 Core Data 类似。

但你可以正常使用,可以为其新建 Category 或者导入头文件就可以直接使用。

注意,自定义code请都选success ,不然就是自定义错误code了,系统已经有failure这个code了

我这里通过 Category 为每个 Intent 类添加了 suggestedInvocationPhrase 属性,可以在用户录制的时候给出建议短语。

@implementation DailyPunchIntent (PXDailyPunch)

- (instancetype)init{

    self = [super init];

    if (self) {

        self.suggestedInvocationPhrase = @"打卡";

    }

    return self;

}

@end

Intents Extension

接下来就是创建 Extension 了。通过 file -> new -> target , 选择 Intents Extension 即可。为了让 Extension 的界面便于控制,我选择了 Include UI Extension。这样就同时创建了两个Extension。

Intents Extension 创建好后,会自动出现一个名为 IntentHandler 的类。对于非即时通讯类的需求,可以删除其他的方法,保留下面一个方法即可:

- (id)handlerForIntent:(INIntent *)intent {

    if ([intent isKindOfClass:[DailyPunchIntent class]]) {

        DailyPunchIntentHandler *intentHandler = [[DailyPunchIntentHandler alloc] init];

        return intentHandler;

    }else if ([intent isKindOfClass:[BreakfastPunchIntent class]]){

        BreakfastPunchIntentHandler *intentHandler = [[BreakfastPunchIntentHandler alloc] init];

        return intentHandler;

    }else if ([intent isKindOfClass:[SportPunchIntent class]]){

        SportPunchIntentHandler *intentHandler = [[SportPunchIntentHandler alloc] init];

        return intentHandler;

    }

    return nil;

}

handlerForIntent 方法是整个 Intents Extension 的入口,当 siri 通过语音指令匹配到对于的 Intent , 该方法就会被执行。这里我 return 我创建一个 DailyPunchIntentHandler 类,该类准守DailyPunchIntentHandling协议。 用来处理匹配到 Intent 后的 UI 显示以及后续操作。

该协议有两个方法:

以下2个方法的调用顺序:intetnt识别后,执行1--> 打开siriUI的界面-->(如果有交互按钮,等点击按钮后再继续,如果没有就继续)-->执行2-->再刷新siriUI的界面

1.该方法是在 siri 匹配到相应的 Intent 时候调用。

通过 completion 返回一个 DailyPunchIntentResponse。

- (void)confirmDailyPunch:(DailyPunchIntent *)intent completion:(void (^)(DailyPunchIntentResponse *response))completion NS_SWIFT_NAME(confirm(intent:completion:));

2.而下面这个方法是用户对 Intent UI 的操作回调,比如用户点击了图一的“是”这个按钮。

- (void)handleDailyPunch:(nonnull DailyPunchIntent *)intent completion:(nonnull void (^)(DailyPunchIntentResponse * _Nonnull))completion;

具体的实现,在-(void)confirmDailyPunch这个方法里,我的需求是要先判断用户是否登录。

如果登录,由 completion 返回的DailyPunchIntentResponse 的 code 为我最初定义的一个状态 DailyPunchIntentResponseCodeFailureUnLogin;

如果已经登录,则返回 DailyPunchIntentResponseCodeReady,表示一切准备就绪。

代码如下:

if(!self.isLogin){

DailyPunchIntentResponse *intentResponse = [[DailyPunchIntentResponse alloc] initWithCode:DailyPunchIntentResponseCodeFailureUnLogin userActivity:nil];

completion(intentResponse); 

}else{

DailyPunchIntentResponse *intentResponse = [[DailyPunchIntentResponse alloc] initWithCode:DailyPunchIntentResponseCodeReady userActivity:nil];

completion(intentResponse); 

}

注意:经测试上面方法建议传initWithCode:DailyPunchIntentResponseCodeReady和initWithCode:DailyPunchIntentResponseCodeSuccess这2个code, 以为自定义code传过去后再siriUI处获取的枚举值都是0

而 - (void)handleDailyPunch方法,最好也要对未登录做处理,这样当提示请用户先登录app的时候,用户点击“是”, 我们可以传递DailyPunchIntentResponseCodeContinueInApp, 那么就会自动启动 APP。

如果是登录状态,那么就去向服务器发送打卡请求:

请求成功,传递 DailyPunchIntentResponseCodeSuccess状态。

请求失败,传递之前自定义的 DailyPunchIntentResponseCodeFailureWithSomething 状态,并且附带上 errorMessage 信息。供后面的 IntentUI使用。

具体如下:

if(self.isLogin){

[[self dailyPunch] subscribeNext:^(id x) {

            completion([[DailyPunchIntentResponse alloc] initWithCode:DailyPunchIntentResponseCodeSuccess userActivity:nil]);

        } error:^(NSError *error) {

            NSString *errorMessage = error.userInfo[@"NSLocalizedDescription"];

            DailyPunchIntentResponse *response = [[DailyPunchIntentResponse alloc] initWithCode:DailyPunchIntentResponseCodeFailureWithSomething userActivity:nil];

            response.errorMessage = errorMessage;

            completion(response);

        }];

}else{

completion([[DailyPunchIntentResponse alloc] initWithCode:DailyPunchIntentResponseCodeFailureRequiringAppLaunch userActivity:nil]);

}

注意在;上面方法的自定义code能在siriUId response.code中获取到

Intents Extension UI

最后就是我们的 Intent UI登场了。打开文件夹目录,会发现系统自动创建了一个名为IntentViewController 的类。

该类只有一个方法,很长的方法:

- (void)configureViewForParameters:(NSSet <INParameter *> *)parameters ofInteraction:(INInteraction *)interaction interactiveBehavior:(INUIInteractiveBehavior)interactiveBehavior context:(INUIHostedViewContext)context completion:(void (^)(BOOL success, NSSet <INParameter *> *configuredParameters, CGSize desiredSize))completion;

1

上面提到的 通过 completion 传递的 DailyPunchIntentResponse,就是传递到该方法。然后通过不同的状态,来展示给用户不同的UI。

需要注意的是,DailyPunchIntentResponse 的 code 如果是系统自动创建的,会和 interaction.intentHandlingStatus 相互对应。

但如果是自定义的状态,他们的 intentHandlingStatus 都对应着 INIntentHandlingStatusSuccess。

先看具体的代码实现:

    [[self.view subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];

    CGSize desiredSize = CGSizeZero;

    if (interaction.intentHandlingStatus == INIntentHandlingStatusReady) {

        desiredSize = [self displayPunchContentFrom:interaction.intent];

    }else if(interaction.intentHandlingStatus == ConnectorDemoIntentResponseCodeInProgress){

        INIntentResponse *response = interaction.intentResponse;

        //每日打卡

      if ([response isKindOfClass:[DailyPunchIntentResponse class]]) {

            DailyPunchIntentResponse *dailyResponse = (DailyPunchIntentResponse *)response;

  if (dailyResponse.code == ConnectorDemoIntentResponseCodeSuccess) { 

} else  if (dailyResponse.code == DailyPunchIntentResponseCodeFailureUnLogin) {

                desiredSize = [self displayPunchUnLoginResultFrom:interaction.intent];

            }else if(dailyResponse.code == DailyPunchIntentResponseCodeFailureWithSomething){

                desiredSize = [self displayPunchFailedResult:dailyResponse.errorMessage];

            }else{

                desiredSize = [self displayPunchSuccessResultFrom:interaction.intent];

            }

        }

    }

    if (CGSizeEqualToSize(desiredSize,CGSizeZero)) {

        completion(NO, [NSSet new], CGSizeZero);

        return;

    }else{

        if (completion) {

            completion(YES, parameters, desiredSize);

        }

    }

逻辑很清楚,先获取 interaction.intentHandlingStatus 的值:

如果是 raedy状态,就正常创建UI;

否则,获取 interaction 的 intentResponse ,从而拿到我们自定义的状态:

根据对应的 code 去创建不同的UI, 总之,别忘了 addSubView。这里以未登录状态的 UI 为例:

- (CGSize)displayPunchUnLoginResultFrom:(INIntent *)intent{

    self.resultView.titleLabel.text = @"请先登录薄荷健康";

    self.resultView.topImageView.image = [UIImage imageNamed:@"ic_failed_siri"];

    [self.view addSubview:self.resultView];

    CGFloat width = 320;

    if (@available(iOS 10.0,*)) {

        width = self.extensionContext.hostedViewMaximumAllowedSize.width;

    }

    CGRect frame = CGRectMake(0, 0, width, 110);

    self.resultView.frame = frame;

    return frame.size;

}

添加语音录制

和上篇博客通过 NSUserActivity的方式类似,唯一的不同就是 INShortcut 初始化方式的不同。

DailyPunchIntent *intent = [[DailyPunchIntent alloc] init];

INShortcut *shortCuts = [[INShortcut alloc] initWithIntent:intent];

INUIAddVoiceShortcutViewController *vc = [[INUIAddVoiceShortcutViewController alloc] initWithShortcut:shortCuts];

vc.delegate = self;

[self presentViewController:vc animated:YES completion:nil];

Donate

每当用户在 app内 有某个行为的时候,你可以选择 Donate ,这样 siri 通过机器学习,智能预测用户未来的行为发生的场景。

只有完成了 Donate ,Siri 才能在正确预测并且出现在屏锁,SportLight 等界面。

DailyPunchIntent *intent = [[DailyPunchIntent alloc] init];

INInteraction *vc = [INInteraction alloc] initWithIntent:intent response:nil];

[interaction donateInteractionWithCompletion:^(NSError * _Nullable error) {

}];

通过Intents Extension UI唤起App

3、在AppDelegate中处理Siri打开APP请求 (Handle Shortcut)

通过userActivity的type值判断是否为Siri Shortcuts呼起,做相应的逻辑处理。

-(BOOL)application:(UIApplication*)application continueUserActivity:(NSUserActivity*)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>>*_Nullable))restorationHandler{

NSLog(@"continueUserActivity");

if([userActivity.activityType isEqualToString:@"loying.LearnSiriShortcut.type"]){

// 做自己的业务逻辑

}

return YES;

}

到这里,已经完成了 iOS12 的 Siri ShortCuts 的核心功能开发。有关获取用户登录状态等和 APP 数据共享的需求,可以参考App Extension 与 App 之间的数据共享这篇文章。

参考资料:

苹果官方WWDC2018视频

苹果官方 Siri ShortCuts Demo


参考链接 :https://blog.csdn.net/u013749108/article/details/81413817

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容