iOS | SiriKit 管理锻炼

公司的项目正好是锻炼类的,也能赶上潮流用上Siri呼唤App了。

看过多篇资料,铺天盖地都是一样的,且并没有明确的锻炼类详解,哪些地方能用哪些地方无法实现,只能自己结合资料、官方文档慢慢摸索,终于还是完成了。
本篇不对SiriKit进行分析,只对如何构建SiriApp进行整理,如下:

一、前提

首先,Siri目前支持以下几个方面:
1、语音通话
2、发送、搜索信息
3、支付
4、照片搜索
5、开始、暂停、恢复、结束、取消锻炼
6、打车

如果你的APP正好支持以上,就能用Siri控制啦。

二、创建

1、打开程序,在主程序下点击+,如图:


添加一个Extension

2、选择iOS -> Intents Extension:


iOS -> Intents Extension

3、填写名字Appname+SiriIntent、选择swift或者oc等设置,如图默认勾选UI Extension:


填写资料

4、选好后会生成一些示例代码,有些教程生成的是锻炼类的代码,有些也和我一样生成的是发送消息搜索消息的代码:


.h.m

5、配置plist
点击第一个plist文件,在
NSExtension -> NSExtensionAttributes -> IntensSupported (常规状态下呼唤Siri)/ IntensRestrictedWhileLocked(锁屏状态下呼唤Siri)中添加字段

(如:默认的会生成INSendMessageIntent、INSearchForMessagesIntent、INSetMessageAttributeIntent,不需要的可以删掉)

按照你所需要的Intent来添加,这里用到的是锻炼类的:INStartWorkoutIntent、INEndWorkoutIntent、INCancelWorkoutIntent、INResumeWorkoutIntent、INPauseWorkoutIntent。


plist

6、打开Siri、并配置主程序plist添加Siri权限,权限也是iOS10新加的,不会的另搜索:

打开Siri
在主程序plist中设置权限

三、代码

1、首先添加协议,管理锻炼用到的协议有:

@interface IntentHandler () <INStartWorkoutIntentHandling, INEndWorkoutIntentHandling, INCancelWorkoutIntentHandling, INResumeWorkoutIntentHandling, INPauseWorkoutIntentHandling>
@end
@implementation IntentHandler

2、这里只说INStartWorkoutIntentHandling,其他类似。

按住command键点击INStartWorkoutIntentHandling进入,可看到有三个阶段的方法:handling method、Confirmation method、Resolution methods。

2.1、先看解析阶段Resolution methods,可根据需要来选择返回哪种类型的参数:

//resolve 解析 Intent 参数
//锻炼类型  
- (void)resolveWorkoutNameForStartWorkout:(INStartWorkoutIntent *)intent withCompletion:(void (^)(INSpeakableStringResolutionResult * _Nonnull))completion{

    NSLog(@"start workoutName = %@",intent.workoutName);
    
    INSpeakableString *text = intent.workoutName;
    
    NSString *workoutName = [NSString stringWithFormat:@"%@",text];
    
    if (text && ![workoutName isEqualToString:@""])
    {
        completion([INSpeakableStringResolutionResult successWithResolvedString:text]);
        
    } else {
        completion([INSpeakableStringResolutionResult needsValue]);
    }

}

//锻炼 是否 受限制
- (void)resolveIsOpenEndedForStartWorkout:(INStartWorkoutIntent *)intent withCompletion:(void (^)(INBooleanResolutionResult * _Nonnull))completion{

    NSLog(@"isOpenEnded = %@",intent.isOpenEnded);
    
    NSNumber *text = intent.isOpenEnded;
    if (text && ![workoutName isEqualToString:@""]) {

//        completion([INBooleanResolutionResult confirmationRequiredWithValueToConfirm:text]);
        completion([INBooleanResolutionResult successWithResolvedValue:text]);
        
    } else {
        completion([INBooleanResolutionResult needsValue]);
    }
}

//锻炼目标
- (void)resolveGoalValueForStartWorkout:(INStartWorkoutIntent *)intent withCompletion:(void (^)(INDoubleResolutionResult * _Nonnull))completion{

    NSLog(@"goalValue = %@",intent.goalValue);
    
    NSNumber *text = intent.goalValue;
    if (text && ![text isKindOfClass:[NSNull class]]) {
        //confirmation方法会对用户进行确认询问 是否根据此目标进行锻炼
//        completion([INDoubleResolutionResult confirmationRequiredWithValueToConfirm:text]);
        completion([INDoubleResolutionResult successWithResolvedValue:[text doubleValue]]);

    } else {
        completion([INDoubleResolutionResult needsValue]);
    }
}

//锻炼时间
- (void)resolveWorkoutGoalUnitTypeForStartWorkout:(INStartWorkoutIntent *)intent withCompletion:(void (^)(INWorkoutGoalUnitTypeResolutionResult * _Nonnull))completion{

    NSLog(@"workoutGoalUnitType = %ld",(long)intent.workoutGoalUnitType);
    
    INWorkoutGoalUnitType text = intent.workoutGoalUnitType;
    if (text) {
        
//        completion([INWorkoutGoalUnitTypeResolutionResult confirmationRequiredWithValueToConfirm:INWorkoutGoalUnitTypeMinute]);
        completion([INWorkoutGoalUnitTypeResolutionResult successWithResolvedValue:INWorkoutGoalUnitTypeMinute]);
        
    } else {
        completion([INWorkoutGoalUnitTypeResolutionResult needsValue]);
    }
}

//室内或室外
- (void)resolveWorkoutLocationTypeForStartWorkout:(INStartWorkoutIntent *)intent withCompletion:(void (^)(INWorkoutLocationTypeResolutionResult * _Nonnull))completion{

    NSLog(@"workoutLocationType = %ld",(long)intent.workoutLocationType);
    
    INWorkoutLocationType text = intent.workoutLocationType;
    if (text) {
//        completion([INWorkoutLocationTypeResolutionResult confirmationRequiredWithValueToConfirm:INWorkoutLocationTypeOutdoor]);
        completion([INWorkoutLocationTypeResolutionResult successWithResolvedValue:INWorkoutLocationTypeOutdoor]);
    } else {
        completion([INWorkoutLocationTypeResolutionResult needsValue]);
    }
}

2.2、确认阶段 Confirmation method:

//confirm 确认请求 并将 UI展示
- (void)confirmStartWorkout:(INStartWorkoutIntent *)intent completion:(void (^)(INStartWorkoutIntentResponse * _Nonnull))completion{

    NSLog(@"start confirm");

    NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INStartWorkoutIntent class])];
    INStartWorkoutIntentResponse *response = [[INStartWorkoutIntentResponse alloc] initWithCode:INStartWorkoutIntentResponseCodeReady userActivity:userActivity];
    
    completion(response);
}

2.3、处理阶段 handling method,此方法是必须的,对Siri返回的运动类型进行判断做出相应操作。

//handle 处理请求
- (void)handleStartWorkout:(INStartWorkoutIntent *)intent completion:(void (^)(INStartWorkoutIntentResponse * _Nonnull))completion{
    
    NSLog(@"start handle");

    NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INStartWorkoutIntent class])];
    
    NSString *workoutName = [NSString stringWithFormat:@"%@",intent.workoutName];
    
    NSLog(@"start workoutName = %@",workoutName);
    //存储相应的字段给NSUserActivity 为后续与主程序通信做准备
    if ([workoutName isEqualToString:@"跑步"] || [workoutName isEqualToString:@"run"])
    {
        userActivity.title = @"start_Running";
    } else if ([workoutName isEqualToString:@"cycle"])
    {
        userActivity.title = @"start_Cycling";
    } else if ([workoutName isEqualToString:@"walk"])
    {
        userActivity.title = @"start_Walking";
    }
    
    //code 参数 选择跳到 APP 中 或者其他
    INStartWorkoutIntentResponse *response = [[INStartWorkoutIntentResponse alloc] initWithCode:INStartWorkoutIntentResponseCodeContinueInApp userActivity:userActivity];
    
    completion(response);
}
INStartWorkoutIntentResponse
另外

这里的INStartWorkoutIntentResponse,按住command键点击进入查看发现有多种返回类型,这里需要我们自己根据APP情况判断后,设置相应的code参数,让Siri做出相应操作,否则会出现【抱歉,你需要在应用中继续操作】等问题。
例如 :
1、正常情况进入APP可选择 ContinueInApp
2、若已有一项运动在开始扔用Siri呼叫开始可选择 FailureOngoingWorkout

四、如何与主程序通信

轻量级通信可采用这个办法:
1、在handling阶段在NSUserActivity存储相应的信息,字典或字符串都行(如上handling阶段代码)。
2、在AppDelegate,获取NSUserActivity所存储的信息。
3、发送通知给所需要的地方。
4、接收到通知后对主程序进行相应操作。

AppDelegate
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler{

    NSArray *workoutArr = [userActivity.title componentsSeparatedByString:@"_"];
    
    NSString *workoutName = workoutArr[1];
    NSString *workoutStatus = workoutArr[0];
    
    NSDictionary *workout = [[NSDictionary alloc]initWithObjectsAndKeys:
                             workoutName,   @"workoutName",
                             workoutStatus, @"workoutStatus",
                             nil];
    
    //创建一个消息对象
    NSNotification *notice = [NSNotification notificationWithName:@"siri" object:nil userInfo:workout];
    //发送消息
    [[NSNotificationCenter defaultCenter]postNotification:notice];

    return YES;
}
- (void)getWorkoutInSiri{
      
    //接收通知
    NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
    //添加观察者
    [center addObserver:self selector:@selector(getSiriInfo:) name:@"siri" object:nil];
}

- (void)getSiriInfo:(NSNotification*)info{
    
    NSLog(@"getSiriInfo = %@",info.userInfo);
}

若集成插件,通知也同样适用:

- (void)getWorkoutInSiri:(CDVInvokedUrlCommand *)command{
    
    self.callBackId = command.callbackId;
    
    //接收通知
    NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
    //添加观察者
    [center addObserver:self selector:@selector(getSiriInfo:) name:@"siri" object:nil];
}

- (void)getSiriInfo:(NSNotification*)info{
    
    NSLog(@"getSiriInfo = %@",info.userInfo);
    
    // send js callback
    CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:info.userInfo];
    [result setKeepCallbackAsBool:YES];
    [self.commandDelegate sendPluginResult:result callbackId:self.callBackId];
}

五、编译

先Run主程序,再Run->Intent,否则不会进入Intent(控制台不会打印log、断点也无法进入),但实际上扩展已经到位了。
在run->Intent时,会弹出一个窗口让你选择想要在哪个App上运行你的扩展,选择你的App即可。

先run主程序、再run->Intent

六、关于默认勾选的UI Extension

看过多篇资料,都有说可以呼出UI界面,也可改变默认界面。但试了多次都无效,查看官方文档发现,好像只支持地图、支付与发送消息三种:


INUIHostedViewSiriProviding

但也有资料说锻炼也会出现,并配有图文证明,这一点就比较懵逼,希望有懂的大神能指点指点!

七、关于【抱歉,你需要在应用中继续操作】

有些资料也会说当遇到 【对不起,你需要在应用里继续】,是因为“我们还需要在工程里添加 CoreLocation 库,确保能添加到我们编译过的 Swift 工程中。”

然而添加后也还是会出现,这时候其实是你没有在三个阶段(handling、Confirmation、Resolution)做出相应的操作,Siri不知道该如何进行下去。

尾言

以上就是在通过查看资料集成SiriKit实战中,遇到的一些问题与解决办法,希望能帮到和我一样正在iOS路上摸索前进的程序猿/媛盆友们~如有理解错误之处望大神指正!

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

推荐阅读更多精彩内容