前言
最近公司App要實現(xiàn)下圖這樣一個功能,對iPhone手機喊 " 嘿,Siri,余額 ”或者 " 嘿,Siri,轉賬 ” 出現(xiàn)下面的列表,結果列表中展示我們的APP。
百度了很久,沒有找到這個是什么功能,有大佬指點我到官網(wǎng)查詢一下,通過查閱發(fā)現(xiàn)官網(wǎng)有一個這樣的文檔 Adding User Interactivity with Siri Shortcuts and the Shortcuts App ,但是通過查閱配置步驟,貌似感覺講的像是設置如何捷徑,感覺自己這個需求又不像是Siri Shortcuts(捷徑)功能。最后有一個朋友給我指點,應該是Siri語音轉賬類。

至此,找對了方向開始調研。
步驟:
一、 工程基本配置
創(chuàng)建一個普通的xcode工程,然后進行如下配置
1、 在工程的 Signing&Capabilities 中,點擊 +Capability ,添加Siri


2、 添加siri權限申請
Privacy - Siri Usage Description
使用siriKit,進行快捷轉賬

3、 在 AppDelegate 中,導入 #import <Intents/Intents.h> 頭文件,添加如下代碼
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[INPreferences requestSiriAuthorization:^(INSiriAuthorizationStatus status) {
NSLog(@"%ld", (long)status);
}];
return YES;
}
此時運行工程,會出現(xiàn)下圖申請權限的界面

點擊好,至此,基本工程配置完畢。
二、 添加Siri擴展
1、 點擊 Xcode -> File -> New -> Target ,選擇 iOS -> Intents Extension 。



填寫 Product Name 點擊 Finish 完成操作, 此時會彈出提示框,選擇 Active 。

至此,會新增兩個 Target , SiriExtension 和 SiriExtensionUI

三、 Target , SiriExtension 和 SiriExtensionUI 配置
1、 對 SiriExtension -> info.plist -> NSExtension -> NSExtensionAttributes 中的鍵值對進行調整,調整前和調整后如下所示:

調整后為:INSendPaymentIntent

2、 對 SiriExtensionUI 也進行相同的配置, SiriExtensionUI 只需要配置 IntentsSupported ,調整后如下:

調整后為:INSendPaymentIntent

基本準備完成,接下來進入代碼編寫。
四、 SiriExtension 編寫代碼
1、 在SiriExtension目錄下,創(chuàng)建一個SiriExtensionIntentHandler : NSObject的類。

2、接下來我們編寫 SiriExtensionIntentHandler類中的代碼
@implementation SiriExtensionIntentHandler
////Resolve - payee 解析收款人 iOS10.0
//- (void)resolvePayeeForSendPayment:(INSendPaymentIntent *)intent withCompletion:(void (^)(INPersonResolutionResult * _Nonnull))completion {
// if (intent.payee == nil || !intent.payee.displayName.length) {
// //如果收款人為空,那么請求siri,需要收款人
// INPersonResolutionResult *resolutionResult = [INPersonResolutionResult notRequired];
// completion(resolutionResult);
// }
// else {
// //收款人不為空,確認收款人信息
// INPersonResolutionResult *resolutionResult = [INPersonResolutionResult successWithResolvedPerson:intent.payee];
// completion(resolutionResult);
// }
//}
////Resolve - payee 解析收款人 iOS11.0
//- (void)resolvePayeeForSendPayment:(INSendPaymentIntent *)intent completion:(void (^)(INSendPaymentPayeeResolutionResult * _Nonnull))completion API_AVAILABLE(ios(11.0)){
// if (intent.payee == nil) {
// INSendPaymentPayeeResolutionResult *resolutionResult = [INSendPaymentPayeeResolutionResult needsValue];
// completion(resolutionResult);
// }
// else {
// INSendPaymentPayeeResolutionResult *resolutionResult = [INSendPaymentPayeeResolutionResult successWithResolvedPerson:intent.payee];
// completion(resolutionResult);
// }
//}
//
//Resolve - currency 解析貨幣 iOS10.0
- (void)resolveCurrencyAmountForSendPayment:(INSendPaymentIntent *)intent withCompletion:(void (^)(INCurrencyAmountResolutionResult * _Nonnull))completion {
INCurrencyAmount *currencyAmount = intent.currencyAmount;
if (currencyAmount == nil) {
//金額為空,請求siri,需要轉賬金額
INCurrencyAmountResolutionResult *resolutionResult = [INCurrencyAmountResolutionResult needsValue];
completion(resolutionResult);
}
else if ([currencyAmount.currencyCode isEqualToString:@"CNY"]) {
//如果幣種不是人民幣,將接收到的幣種轉化為人民幣
INCurrencyAmount *newCurrencyAmount = [[INCurrencyAmount alloc] initWithAmount:currencyAmount.amount currencyCode:@"CNY"];
INCurrencyAmountResolutionResult *resolutionResult = [INCurrencyAmountResolutionResult successWithResolvedCurrencyAmount:newCurrencyAmount];
completion(resolutionResult);
}
else {
INCurrencyAmountResolutionResult *resolutionResult = [INCurrencyAmountResolutionResult successWithResolvedCurrencyAmount:currencyAmount];
completion(resolutionResult);
}
}
//Resolve - currency 解析貨幣單位 iOS11.0
- (void)resolveCurrencyAmountForSendPayment:(INSendPaymentIntent *)intent completion:(void (^)(INSendPaymentCurrencyAmountResolutionResult * _Nonnull))completion API_AVAILABLE(ios(11.0)){
INCurrencyAmount *currencyAmount = intent.currencyAmount;
if (currencyAmount == nil || currencyAmount.amount == nil) {
INSendPaymentCurrencyAmountResolutionResult *resolutionResult = [INSendPaymentCurrencyAmountResolutionResult needsValue];
completion(resolutionResult);
}
else if (![currencyAmount.currencyCode isEqualToString:@"CNY"]) {
//貨幣格式轉化為人民幣
INCurrencyAmount *newCurrencyAmount = [[INCurrencyAmount alloc] initWithAmount:currencyAmount.amount currencyCode:@"CNY"];
INSendPaymentCurrencyAmountResolutionResult *resolutionResult = [INSendPaymentCurrencyAmountResolutionResult successWithResolvedCurrencyAmount:newCurrencyAmount];
completion(resolutionResult);
} else {
INSendPaymentCurrencyAmountResolutionResult *resolutionResult = [INSendPaymentCurrencyAmountResolutionResult successWithResolvedCurrencyAmount:currencyAmount];
completion(resolutionResult);
}
}
//Confirm - 確認金額信息
- (void)confirmSendPayment:(INSendPaymentIntent *)intent completion:(void (^)(INSendPaymentIntentResponse * _Nonnull))completion {
NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INSendPaymentIntent class])];
userActivity.title = @"轉賬";
userActivity.userInfo = @{@"displayName": intent.payee.displayName?:@"",
@"amount": intent.currencyAmount.amount};
//確認支付貨幣,否則系統(tǒng)會默認展示USD(美元)
INPaymentMethod *paymentMethod = [INPaymentMethod applePayPaymentMethod];
//status字段決定了siri轉賬頁面底部的UI
INPaymentRecord *paymentRecord = [[INPaymentRecord alloc] initWithPayee:intent.payee payer:nil currencyAmount:intent.currencyAmount paymentMethod:paymentMethod note:intent.note status:(INPaymentStatusPending)];
INSendPaymentIntentResponse *sendPaymentIntentResponse = [[INSendPaymentIntentResponse alloc] initWithCode:(INSendPaymentIntentResponseCodeReady) userActivity:userActivity];
sendPaymentIntentResponse.paymentRecord = paymentRecord;
completion(sendPaymentIntentResponse);
}
//Handle - 轉賬處理
- (void)handleSendPayment:(INSendPaymentIntent *)intent completion:(void (^)(INSendPaymentIntentResponse * _Nonnull))completion {
NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSStringFromClass([INSendPaymentIntent class])];
userActivity.title = @"轉賬";
userActivity.userInfo = @{@"displayName": intent.payee.displayName?:@"",
@"amount": intent.currencyAmount.amount};
INSendPaymentIntentResponse *sendPaymentIntentResponse = [[INSendPaymentIntentResponse alloc] initWithCode:(INSendPaymentIntentResponseCodeInProgress) userActivity:userActivity];
completion(sendPaymentIntentResponse);
}
@end
3、 編寫SiriExtensionUI --> IntentViewController 類中代碼的實現(xiàn)
3.1 首先編寫SiriExtensionUI --> MainInterface.storyboard的UI界面

3.2 編寫IntentViewController類中代碼的實現(xiàn):
#import "IntentViewController.h"
#import <Intents/Intents.h>
@interface IntentViewController ()<INUIHostedViewSiriProviding>
@property (weak, nonatomic) IBOutlet UILabel *amountLabel;
@end
@implementation IntentViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
#pragma mark - INUIHostedViewSiriProviding
- (BOOL)displaysPaymentTransaction {
return YES;;
}
#pragma mark - INUIHostedViewControlling
- (void)configureWithInteraction:(INInteraction *)interaction context:(INUIHostedViewContext)context completion:(void (^)(CGSize))completion {
if ([interaction.intent isKindOfClass:[INSendPaymentIntent class]] && (interaction.intentHandlingStatus == INIntentHandlingStatusReady)) {
INSendPaymentIntent *sendPaymentIntent = (INSendPaymentIntent *)interaction.intent;
self.amountLabel.text = [NSString stringWithFormat:@"¥%.2f", sendPaymentIntent.currencyAmount.amount.doubleValue];
if (completion) {
completion(CGSizeMake([self desiredSize].width, 200));
}
} else {
if (completion) {
completion(CGSizeZero);
}
}
}
// Prepare your view controller for the interaction to handle.
- (void)configureViewForParameters:(NSSet <INParameter *> *)parameters ofInteraction:(INInteraction *)interaction interactiveBehavior:(INUIInteractiveBehavior)interactiveBehavior context:(INUIHostedViewContext)context completion:(void (^)(BOOL success, NSSet <INParameter *> *configuredParameters, CGSize desiredSize))completion API_AVAILABLE(ios(11.0)){
// Do configuration here, including preparing views and calculating a desired size for presentation.
if ([interaction.intent isKindOfClass:[INSendPaymentIntent class]] &&
(interaction.intentHandlingStatus == INIntentHandlingStatusReady) &&
[[parameters anyObject].parameterKeyPath isEqualToString:@"currencyAmount"]) {
INSendPaymentIntent *sendPaymentIntent = (INSendPaymentIntent *)interaction.intent;
self.amountLabel.text = [NSString stringWithFormat:@"¥%.2f", sendPaymentIntent.currencyAmount.amount.doubleValue];
completion(YES, parameters, CGSizeMake([self desiredSize].width, 200));
} else {
completion(YES, parameters, CGSizeZero);
}
}
- (CGSize)desiredSize {
return [self extensionContext].hostedViewMaximumAllowedSize;
}
@end
至此,所有代碼的編寫已經(jīng)完成,此時我們跑一下真機,可以看到APP已經(jīng)安裝到我們手機,對iPhone手機喊 " 嘿,Siri,余額 ”或者 " 嘿,Siri,轉賬 ” 出現(xiàn)下面的列表,結果列表中展示我們的APP了。

注意:
- 我們的Demo中有
SiriExtension和SiriExtensionUI擴展,所以這兩個擴展的bundleID要和主工程保持一樣的規(guī)范。
例:
主工程bundleID:com.ddq.siriextension
SiriExtension擴展的bundleID:com.ddq.siriextension.SiriExtension
SiriExtensionUI擴展的bundleID:com.ddq.siriextension.SiriExtensionUI
要在主工程的后面,添加對應的點“.”后綴名。
證書的創(chuàng)建亦是如此:
image.png
2.主工程的bundleID需要開啟證書的Siri功能;

3.如果小伙伴下載我的demo運行報錯,需要替換主工程和target為你們自己的bundleID。
