公司的项目正好是锻炼类的,也能赶上潮流用上Siri呼唤App了。
看过多篇资料,铺天盖地都是一样的,且并没有明确的锻炼类详解,哪些地方能用哪些地方无法实现,只能自己结合资料、官方文档慢慢摸索,终于还是完成了。
本篇不对SiriKit进行分析,只对如何构建SiriApp进行整理,如下:
一、前提
首先,Siri目前支持以下几个方面:
1、语音通话
2、发送、搜索信息
3、支付
4、照片搜索
5、开始、暂停、恢复、结束、取消锻炼
6、打车
如果你的APP正好支持以上,就能用Siri控制啦。
二、创建
1、打开程序,在主程序下点击+,如图:
2、选择iOS -> Intents Extension:
3、填写名字Appname+SiriIntent、选择swift或者oc等设置,如图默认勾选UI Extension:
4、选好后会生成一些示例代码,有些教程生成的是锻炼类的代码,有些也和我一样生成的是发送消息搜索消息的代码:
5、配置plist
点击第一个plist文件,在
NSExtension -> NSExtensionAttributes -> IntensSupported (常规状态下呼唤Siri)/ IntensRestrictedWhileLocked(锁屏状态下呼唤Siri)中添加字段
(如:默认的会生成INSendMessageIntent、INSearchForMessagesIntent、INSetMessageAttributeIntent,不需要的可以删掉)
按照你所需要的Intent来添加,这里用到的是锻炼类的:INStartWorkoutIntent、INEndWorkoutIntent、INCancelWorkoutIntent、INResumeWorkoutIntent、INPauseWorkoutIntent。
6、打开Siri、并配置主程序plist添加Siri权限,权限也是iOS10新加的,不会的另搜索:
三、代码
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,按住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即可。
六、关于默认勾选的UI Extension
看过多篇资料,都有说可以呼出UI界面,也可改变默认界面。但试了多次都无效,查看官方文档发现,好像只支持地图、支付与发送消息三种:
但也有资料说锻炼也会出现,并配有图文证明,这一点就比较懵逼,希望有懂的大神能指点指点!
七、关于【抱歉,你需要在应用中继续操作】
有些资料也会说当遇到 【对不起,你需要在应用里继续】,是因为“我们还需要在工程里添加 CoreLocation 库,确保能添加到我们编译过的 Swift 工程中。”
然而添加后也还是会出现,这时候其实是你没有在三个阶段(handling、Confirmation、Resolution)做出相应的操作,Siri不知道该如何进行下去。
尾言
以上就是在通过查看资料集成SiriKit实战中,遇到的一些问题与解决办法,希望能帮到和我一样正在iOS路上摸索前进的程序猿/媛盆友们~如有理解错误之处望大神指正!