swift3.0iOS相关新技术iOS Developer

iOS | SiriKit 管理锻炼

2016-10-18  本文已影响1188人  麋鹿的兔

公司的项目正好是锻炼类的,也能赶上潮流用上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路上摸索前进的程序猿/媛盆友们~如有理解错误之处望大神指正!

上一篇下一篇

猜你喜欢

热点阅读