iOS12 Siri ShortCuts 應(yīng)用 通過(guò)Intents方式實(shí)現(xiàn)【轉(zhuǎn)】

我的demo(內(nèi)含詳細(xì)注解):https://github.com/huchuankan/IOSDemo

ios14補(bǔ)充,ios的Siri呼喚出來(lái)從全屏變成了 home鍵上面的彩色圈圈,這種模式下好像無(wú)法用快捷命令的短語(yǔ)打開(kāi)界面, 我的方式是 設(shè)置--siri 里面打開(kāi)“鍵入以使用Siri”按鈕, 這樣在呼喚 ”嘿 siri“的時(shí)候,還是全屏的siri模式,這時(shí)候說(shuō)出短語(yǔ),就能實(shí)現(xiàn)功能, 個(gè)人琢磨不知道為啥,也不知道有沒(méi)有其他辦法

原文有一點(diǎn)小坑,下文為我demo后的改進(jìn),可對(duì)比閱讀

今天這篇文章就來(lái)介紹另外一種功能,通過(guò) Intents Extension 實(shí)現(xiàn)不打開(kāi)app 去完成某個(gè)任務(wù)。先來(lái)看下效果:

SiriKit Intent Definition File

在新建Extension之前,我們要通過(guò) file->newfile, 選擇 SiriKit Intent Definition File。創(chuàng)建好后是這個(gè)樣子的:

上圖中,我一共自定義了三個(gè) Intent, 分別是 DailyPunch、BreakfastPunch和 SportPunch。在這個(gè)界面,你可以設(shè)置 Intent 的 category,title和一些參數(shù)。每個(gè) Intent 都對(duì)應(yīng)一個(gè) Response, 如下圖所示。你可以在response定義參數(shù)和錯(cuò)誤類型。如圖所示,我定義了 errorMessage 的參數(shù) 和 failureUnLogin等兩個(gè)錯(cuò)誤類型。這里 errorMessage 用來(lái)傳遞服務(wù)器返回的錯(cuò)誤信息。

注意,需要交互按鈕的就勾選 User confirmation required

創(chuàng)建完成后,編譯一下項(xiàng)目,xcode 會(huì)自動(dòng)生成對(duì)應(yīng)的類,我這里的話會(huì)生成 DailyPunchIntent 等三個(gè)類,每個(gè)類包含了 DailyPunchIntentHandling 協(xié)議和 DailyPunchIntentResponse 類等所需要的內(nèi)容。

需要注意的是,這些類不會(huì)出現(xiàn)在項(xiàng)目的目錄中,有點(diǎn)和 Core Data 類似。

但你可以正常使用,可以為其新建 Category 或者導(dǎo)入頭文件就可以直接使用。

注意,自定義code請(qǐng)都選success ,不然就是自定義錯(cuò)誤code了,系統(tǒng)已經(jīng)有failure這個(gè)code了

我這里通過(guò) Category 為每個(gè) Intent 類添加了 suggestedInvocationPhrase 屬性,可以在用戶錄制的時(shí)候給出建議短語(yǔ)。

@implementation DailyPunchIntent (PXDailyPunch)

- (instancetype)init{

? ? self = [super init];

? ? if (self) {

? ? ? ? self.suggestedInvocationPhrase = @"打卡";

? ? }

? ? return self;

}

@end

Intents Extension

接下來(lái)就是創(chuàng)建 Extension 了。通過(guò) file -> new -> target , 選擇 Intents Extension 即可。為了讓 Extension 的界面便于控制,我選擇了 Include UI Extension。這樣就同時(shí)創(chuàng)建了兩個(gè)Extension。

Intents Extension 創(chuàng)建好后,會(huì)自動(dòng)出現(xiàn)一個(gè)名為 IntentHandler 的類。對(duì)于非即時(shí)通訊類的需求,可以刪除其他的方法,保留下面一個(gè)方法即可:

- (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?方法是整個(gè) Intents Extension 的入口,當(dāng) siri 通過(guò)語(yǔ)音指令匹配到對(duì)于的 Intent , 該方法就會(huì)被執(zhí)行。這里我 return 我創(chuàng)建一個(gè) DailyPunchIntentHandler 類,該類準(zhǔn)守DailyPunchIntentHandling協(xié)議。 用來(lái)處理匹配到 Intent 后的 UI 顯示以及后續(xù)操作。

該協(xié)議有兩個(gè)方法:

以下2個(gè)方法的調(diào)用順序:intetnt識(shí)別后,執(zhí)行1--> 打開(kāi)siriUI的界面-->(如果有交互按鈕,等點(diǎn)擊按鈕后再繼續(xù),如果沒(méi)有就繼續(xù))-->執(zhí)行2-->再刷新siriUI的界面

1.該方法是在 siri 匹配到相應(yīng)的 Intent 時(shí)候調(diào)用。

通過(guò) completion 返回一個(gè) DailyPunchIntentResponse。

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

2.而下面這個(gè)方法是用戶對(duì) Intent UI 的操作回調(diào),比如用戶點(diǎn)擊了圖一的“是”這個(gè)按鈕。

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

具體的實(shí)現(xiàn),在-(void)confirmDailyPunch這個(gè)方法里,我的需求是要先判斷用戶是否登錄。

如果登錄,由 completion 返回的DailyPunchIntentResponse 的 code 為我最初定義的一個(gè)狀態(tài) DailyPunchIntentResponseCodeFailureUnLogin;

如果已經(jīng)登錄,則返回 DailyPunchIntentResponseCodeReady,表示一切準(zhǔn)備就緒。

代碼如下:

if(!self.isLogin){

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

completion(intentResponse);?

}else{

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

completion(intentResponse);?

}

注意:經(jīng)測(cè)試上面方法建議傳initWithCode:DailyPunchIntentResponseCodeReady和initWithCode:DailyPunchIntentResponseCodeSuccess這2個(gè)code, 以為自定義code傳過(guò)去后再siriUI處獲取的枚舉值都是0

而?- (void)handleDailyPunch方法,最好也要對(duì)未登錄做處理,這樣當(dāng)提示請(qǐng)用戶先登錄app的時(shí)候,用戶點(diǎn)擊“是”, 我們可以傳遞DailyPunchIntentResponseCodeContinueInApp, 那么就會(huì)自動(dòng)啟動(dòng) APP。

如果是登錄狀態(tài),那么就去向服務(wù)器發(fā)送打卡請(qǐng)求:

請(qǐng)求成功,傳遞?DailyPunchIntentResponseCodeSuccess狀態(tài)。

請(qǐng)求失敗,傳遞之前自定義的?DailyPunchIntentResponseCodeFailureWithSomething?狀態(tài),并且附帶上 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登場(chǎng)了。打開(kāi)文件夾目錄,會(huì)發(fā)現(xiàn)系統(tǒng)自動(dòng)創(chuàng)建了一個(gè)名為IntentViewController?的類。

該類只有一個(gè)方法,很長(zhǎng)的方法:

- (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

上面提到的 通過(guò) completion 傳遞的 DailyPunchIntentResponse,就是傳遞到該方法。然后通過(guò)不同的狀態(tài),來(lái)展示給用戶不同的UI。

需要注意的是,DailyPunchIntentResponse 的 code 如果是系統(tǒng)自動(dòng)創(chuàng)建的,會(huì)和 interaction.intentHandlingStatus 相互對(duì)應(yīng)。

但如果是自定義的狀態(tài),他們的 intentHandlingStatus 都對(duì)應(yīng)著 INIntentHandlingStatusSuccess。

先看具體的代碼實(shí)現(xiàn):

? ? [[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狀態(tài),就正常創(chuàng)建UI;

否則,獲取 interaction 的 intentResponse ,從而拿到我們自定義的狀態(tài):

根據(jù)對(duì)應(yīng)的 code 去創(chuàng)建不同的UI, 總之,別忘了 addSubView。這里以未登錄狀態(tài)的 UI 為例:

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

? ? self.resultView.titleLabel.text = @"請(qǐng)先登錄薄荷健康";

? ? 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;

}

添加語(yǔ)音錄制

和上篇博客通過(guò) 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

每當(dāng)用戶在 app內(nèi) 有某個(gè)行為的時(shí)候,你可以選擇 Donate ,這樣 siri 通過(guò)機(jī)器學(xué)習(xí),智能預(yù)測(cè)用戶未來(lái)的行為發(fā)生的場(chǎng)景。

只有完成了 Donate ,Siri 才能在正確預(yù)測(cè)并且出現(xiàn)在屏鎖,SportLight 等界面。

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

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

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

}];

通過(guò)Intents Extension UI喚起App

3、在AppDelegate中處理Siri打開(kāi)APP請(qǐng)求 (Handle Shortcut)

通過(guò)userActivity的type值判斷是否為Siri Shortcuts呼起,做相應(yīng)的邏輯處理。

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

NSLog(@"continueUserActivity");

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

// 做自己的業(yè)務(wù)邏輯

}

return YES;

}

到這里,已經(jīng)完成了 iOS12 的 Siri ShortCuts 的核心功能開(kāi)發(fā)。有關(guān)獲取用戶登錄狀態(tài)等和 APP 數(shù)據(jù)共享的需求,可以參考App Extension 與 App 之間的數(shù)據(jù)共享這篇文章。

參考資料:

蘋(píng)果官方WWDC2018視頻

蘋(píng)果官方 Siri ShortCuts Demo


參考鏈接 :https://blog.csdn.net/u013749108/article/details/81413817

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容