iOS | SiriKit 管理鍛煉

公司的項目正好是鍛煉類的,也能趕上潮流用上Siri呼喚App了瞧栗。

看過多篇資料迹恐,鋪天蓋地都是一樣的殴边,且并沒有明確的鍛煉類詳解锤岸,哪些地方能用哪些地方無法實現(xiàn)是偷,只能自己結合資料蛋铆、官方文檔慢慢摸索栗恩,終于還是完成了磕秤。
本篇不對SiriKit進行分析市咆,只對如何構建SiriApp進行整理蒙兰,如下:

一、前提

首先针炉,Siri目前支持以下幾個方面:
1篡帕、語音通話
2镰烧、發(fā)送茉唉、搜索信息
3、支付
4赌渣、照片搜索
5、開始斜姥、暫停缚忧、恢復闪水、結束球榆、取消鍛煉
6、打車

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

二穗椅、創(chuàng)建

1脆烟、打開程序,在主程序下點擊+房待,如圖:


添加一個Extension

2邢羔、選擇iOS -> Intents Extension:


iOS -> Intents Extension

3、填寫名字Appname+SiriIntent桑孩、選擇swift或者oc等設置拜鹤,如圖默認勾選UI Extension:


填寫資料

4、選好后會生成一些示例代碼流椒,有些教程生成的是鍛煉類的代碼敏簿,有些也和我一樣生成的是發(fā)送消息搜索消息的代碼:


.h.m

5绣硝、配置plist
點擊第一個plist文件够傍,在
NSExtension -> NSExtensionAttributes -> IntensSupported (常規(guī)狀態(tài)下呼喚Siri)/ IntensRestrictedWhileLocked(鎖屏狀態(tài)下呼喚Siri)中添加字段

(如:默認的會生成INSendMessageIntent安聘、INSearchForMessagesIntent囱桨、INSetMessageAttributeIntent翠语,不需要的可以刪掉)

按照你所需要的Intent來添加酣难,這里用到的是鍛煉類的:INStartWorkoutIntent菜谣、INEndWorkoutIntent待笑、INCancelWorkoutIntent齐邦、INResumeWorkoutIntent丐吓、INPauseWorkoutIntent。


plist

6勘伺、打開Siri缅帘、并配置主程序plist添加Siri權限,權限也是iOS10新加的,不會的另搜索:

打開Siri
在主程序plist中設置權限

三按价、代碼

1框产、首先添加協(xié)議,管理鍛煉用到的協(xié)議有:

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

2窑多、這里只說INStartWorkoutIntentHandling遥巴,其他類似。

按住command鍵點擊INStartWorkoutIntentHandling進入携栋,可看到有三個階段的方法:handling method鸯隅、Confirmation method蝌以、Resolution methods碍彭。

2.1庇忌、先看解析階段Resolution methods晃酒,可根據(jù)需要來選擇返回哪種類型的參數(shù):

//resolve 解析 Intent 參數(shù)
//鍛煉類型  
- (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方法會對用戶進行確認詢問 是否根據(jù)此目標進行鍛煉
//        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 為后續(xù)與主程序通信做準備
    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 參數(shù) 選擇跳到 APP 中 或者其他
    INStartWorkoutIntentResponse *response = [[INStartWorkoutIntentResponse alloc] initWithCode:INStartWorkoutIntentResponseCodeContinueInApp userActivity:userActivity];
    
    completion(response);
}
INStartWorkoutIntentResponse
另外

這里的INStartWorkoutIntentResponse,按住command鍵點擊進入查看發(fā)現(xiàn)有多種返回類型荠诬,這里需要我們自己根據(jù)APP情況判斷后琅翻,設置相應的code參數(shù),讓Siri做出相應操作柑贞,否則會出現(xiàn)【抱歉方椎,你需要在應用中繼續(xù)操作】等問題。
例如 :
1钧嘶、正常情況進入APP可選擇 ContinueInApp
2棠众、若已有一項運動在開始扔用Siri呼叫開始可選擇 FailureOngoingWorkout

四、如何與主程序通信

輕量級通信可采用這個辦法:
1有决、在handling階段在NSUserActivity存儲相應的信息闸拿,字典或字符串都行(如上handling階段代碼)。
2书幕、在AppDelegate新荤,獲取NSUserActivity所存儲的信息。
3台汇、發(fā)送通知給所需要的地方苛骨。
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];
    
    //創(chuàng)建一個消息對象
    NSNotification *notice = [NSNotification notificationWithName:@"siri" object:nil userInfo:workout];
    //發(fā)送消息
    [[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界面,也可改變默認界面。但試了多次都無效测蘑,查看官方文檔發(fā)現(xiàn)灌危,好像只支持地圖、支付與發(fā)送消息三種:


INUIHostedViewSiriProviding

但也有資料說鍛煉也會出現(xiàn)碳胳,并配有圖文證明勇蝙,這一點就比較懵逼,希望有懂的大神能指點指點挨约!

七味混、關于【抱歉,你需要在應用中繼續(xù)操作】

有些資料也會說當遇到 【對不起诫惭,你需要在應用里繼續(xù)】翁锡,是因為“我們還需要在工程里添加 CoreLocation 庫,確保能添加到我們編譯過的 Swift 工程中贝攒〉撂埽”

然而添加后也還是會出現(xiàn)时甚,這時候其實是你沒有在三個階段(handling隘弊、Confirmation、Resolution)做出相應的操作荒适,Siri不知道該如何進行下去梨熙。

尾言

以上就是在通過查看資料集成SiriKit實戰(zhàn)中,遇到的一些問題與解決辦法刀诬,希望能幫到和我一樣正在iOS路上摸索前進的程序猿/媛盆友們~如有理解錯誤之處望大神指正咽扇!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陕壹,隨后出現(xiàn)的幾起案子质欲,更是在濱河造成了極大的恐慌,老刑警劉巖糠馆,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘶伟,死亡現(xiàn)場離奇詭異,居然都是意外死亡又碌,警方通過查閱死者的電腦和手機九昧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毕匀,“玉大人铸鹰,你說我怎么就攤上這事≡聿恚” “怎么了蹋笼?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我剖毯,道長诞仓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任速兔,我火速辦了婚禮墅拭,結果婚禮上,老公的妹妹穿的比我還像新娘涣狗。我一直安慰自己谍婉,他們只是感情好,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布镀钓。 她就那樣靜靜地躺著穗熬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丁溅。 梳的紋絲不亂的頭發(fā)上唤蔗,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音窟赏,去河邊找鬼妓柜。 笑死,一個胖子當著我的面吹牛涯穷,可吹牛的內容都是我干的棍掐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼拷况,長吁一口氣:“原來是場噩夢啊……” “哼作煌!你這毒婦竟也來了?” 一聲冷哼從身側響起赚瘦,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤粟誓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后起意,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹰服,經...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年杜恰,在試婚紗的時候發(fā)現(xiàn)自己被綠了获诈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡心褐,死狀恐怖舔涎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情逗爹,我是刑警寧澤亡嫌,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布嚎于,位于F島的核電站,受9級特大地震影響挟冠,放射性物質發(fā)生泄漏于购。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一知染、第九天 我趴在偏房一處隱蔽的房頂上張望肋僧。 院中可真熱鬧,春花似錦控淡、人聲如沸嫌吠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辫诅。三九已至,卻和暖如春涧狮,著一層夾襖步出監(jiān)牢的瞬間炕矮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工者冤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肤视,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓譬嚣,卻偏偏與公主長得像钢颂,于是被迫代替她去往敵國和親钞它。 傳聞我的和親對象是個殘疾皇子拜银,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內容