引言
- 在6月14日凌晨的WWDC2016大會上,蘋果提出iOS10是一次里程碑并且推出了十個新特性,homekit、messageapp等等,大部分是基于iPhone原生應用的更新。其中最大的亮點之一是Siri的接口開放,在iOS10中提供了SiriKit框架在用戶使用Siri的時候生成INExtension對象來告知我們的應用,我們可以通過SiriKit提供的API展示給用戶更多的內容。
- Siri通過語言處理系統對用戶發出的對話請求進行解析之后生成一個用來描述對話內容的Intents事件,然后通過SiriKit框架分發給集成框架的應用程序以此來獲取應用的內容,比如通過文字匹配查找應用聊天記錄、聊天對象等功能,此外還支持為用戶使用蘋果地圖時提供應用內置服務等功能。
SiriKit六類服務
sirikit 服務 | 對應的INintent |
---|---|
語音和視頻通話 VoIP calling | INStartVideoCallIntent、INStartAudioCallIntent |
發送消息 Messaging | INSendMessageIntent |
收款或者付款 Payments | INSendPaymentIntent、INRequestPaymentIntent |
圖片搜索 Photo search | INSearchForPhotosIntent |
管理鍛煉 Workouts | INEndWorkoutIntent、INPauseWorkoutIntent 、INStartWorkoutIntent 、 INResumeWorkoutIntent 、INCancelWorkoutIntent |
行程預約 Ride booking | INRequestRideIntent、INGetRideStatusIntent、 INListRideOptionsIntent、 INGetRideStatusIntent |
更加詳細的說明參見Intents Domains
也就是說當我們使用sirikit的時候,可以使用以上列表對應的六種服務,比如說你的應用是聯系人可以發送消息,這時候你就可以使用INSendMessageIntent服務,在鎖屏或者主屏幕喚起siri,然后比方說說出“嘿,siri,在xxx應用上發送一條消息給劍劍”,然后siri就能響應并同時提供回調給你的應用siri extension。具體的流程我們會在下一節舉例說明。
Siri執行流程
- 示例流程圖
-
獲取詞匯邏輯
siri_cihui.png
從詞匯中可以看到intent(暫且翻譯成意圖?)對應著siri給我們的響應,這是app通過處理intent對應的reslove,confirm,handle來做相關響應。
intents
1.intents說明
- Action to be performed 定義將要響應的動作,即reslove,confirm,handle
- Zero-to-many parameters 意圖會有對應的參數來響應某些事件
- Classified into a domain 意圖劃分的種類
Siri通過Intents extension的擴展方式和我們的應用進行交互,其中INExtension扮演著Intents extension擴展中直接協同Siri共同響應用戶的角色。當我們實現了Intents extension擴展并產生了一個Siri請求事件時,Intents事件的處理過程分Resolve、Confirm和Handle三個步驟。
2.LifeCycle for an intent
一個intent的完整周期如下圖:
- Resolve階段。在Siri獲取用戶的語音輸入之后,生成一個INIntent對象,將語音中的關鍵信息提取出來并且填充對應的屬性,該對象會傳遞給我們設置好的INExtension子類對象進行處理,根據子類遵循的不同protocol來選擇不同的解決方案。在上一個階段通過handler(for intent:)返回了處理intent的對象,此階段會依次調用confirm打頭的實例方法來判斷Siri填充的信息是否完成。
resolve階段用戶大致分為以下說明階段:
助SIri明白用戶的含義
影響Siri的行為
提供resolution response
successWithResolvedPerson:成功找到匹配的人
disambiguationWithPeopleToDisambiguate:還需要挑選
confirmationRequiredWithPersonToConfirm:還需要確認下
needMoreDetailsForPerson:還需要更具體的信息,需要Siri進行詢問
unsupportedWithReason:無法使用指定值
needsValue:需要某些必需值
notRequired:應用并沒有要求某些值
- Confirm階段。確認信息。檢查必要的狀態等。Siri進行最后的處理階段,生成答復對象,并且向此intent對象確認處理結果。
告訴Siri預期結果
檢查必要的狀態
提供Intent response
Siri提供必要的確認提示,參考如下連圖例
- Handle階段:在Confirm方法執行完成之后,然后顯示結果給用戶看
執行請求操作
提供有關結果足夠精確的信息
如果結果耗時的話還可提供loading
實現一個Siri Kit應用
- 升級到Xcode8,一臺升級到iOS10的測試設備
-
Intents extension : resolve、confirm、handle流程
新建extension如下圖:
siri_ex2.png
通常SiriUI也會配置一起創建,入后配置info.plist文件
上圖中IntentsSupported是指你的擴展支持的intent類型,如果你的代碼想支持某種intent必須在這里配置。
IntentsRestrictedWhileLocked是指在鎖屏狀態下能用siri喚起的intent,如果你想在鎖屏下能喚起siri的擴展功能必須配置。
創建之后,查看plist文件
- 1.增加xxxintent
- 2.NSExtensionPrincipalClass,這里必須在plist聲明,它是INExtension的子類,INIntentHandleProviding,handleForIntent,handleClass must conform to specific intent handling protocol等,詳細說明參見Intents Domains
主項目plist 增加NSSiriUsageDescription 這個是請求Siri權限時提示的文案
使用Siri時,用戶必須說出App的名字,也就是Bundle display name
以發送消息為例:當siri第一次確認的時候,需要應用授權,授權代碼和提示如下圖:
具體intent的聲明周期代碼參考蘋果官方demo,代碼示例如下:
// MARK: 1. Resolve
func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: ([INPersonResolutionResult]) -> Swift.Void) {
if let recipients = intent.recipients {
var resolutionResults = [INPersonResolutionResult]()
for recipient in recipients {
let matchingContacts = UCAddressBookManager().contacts(matchingName: recipient.displayName)
switch matchingContacts.count {
case 2 ... Int.max:
// We need Siri's help to ask user to pick one from the matches.
let disambiguationOptions: [INPerson] = matchingContacts.map { contact in
return contact.inPerson()
}
resolutionResults += [INPersonResolutionResult.disambiguation(with: disambiguationOptions)]
case 1:
let recipientMatched = matchingContacts[0].inPerson()
resolutionResults += [INPersonResolutionResult.success(with: recipientMatched)]
case 0:
resolutionResults += [INPersonResolutionResult.unsupported(with: INIntentResolutionResultUnsupportedReason.none)]
default:
break
}
}
completion(resolutionResults)
} else {
// No recipients are provided. We need to prompt for a value.
completion([INPersonResolutionResult.needsValue()])
}
}
func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: (INStringResolutionResult) -> Swift.Void) {
if let text = intent.content where !text.isEmpty {
completion(INStringResolutionResult.success(with: text))
}
else {
completion(INStringResolutionResult.needsValue())
}
}
// MARK: 2. Confirm
func confirm(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Swift.Void) {
if UCAccount.shared().hasValidAuthentication {
completion(INSendMessageIntentResponse.init(code: INSendMessageIntentResponseCode.success, userActivity: nil))
}
else {
// Creating our own user activity to include error information.
let userActivity = NSUserActivity.init(activityType: String(INSendMessageIntent))
userActivity.userInfo = [NSString(string: "error"):NSString(string: "UserLoggedOut")]
completion(INSendMessageIntentResponse.init(code: INSendMessageIntentResponseCode.failureRequiringAppLaunch, userActivity: userActivity))
}
}
// MARK: 3. Handle
func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Swift.Void) {
if intent.recipients != nil && intent.content != nil {
// Send the message.
let success = UCAccount.shared().sendMessage(intent.content, toRecipients: intent.recipients)
completion(INSendMessageIntentResponse.init(code: success ? .success : .failure, userActivity: nil))
}
else {
completion(INSendMessageIntentResponse.init(code: INSendMessageIntentResponseCode.failure, userActivity: nil))
}
}
- Intents UI extension 提供界面自定義等內容
通常確認界面的UI蘋果會提供默認的樣式,默認的樣式如下圖:
但是如果你想自定義UI,如下圖所示,你就必須配置Intents UI extension,在MainInterface.storyboard配置相關UI,然后在對應的控制器中配置config函數。
圖
參考configs函數如下:
func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
var size: CGSize
// Check if the interaction describes a SendMessageIntent.
if interaction.representsSendMessageIntent {
// If it is, let's set up a view controller.
let chatViewController = UCChatViewController()
chatViewController.messageContent = interaction.messageContent
let contact = UCContact()
contact.name = interaction.recipientName
chatViewController.recipient = contact
switch interaction.intentHandlingStatus {
case INIntentHandlingStatus.unspecified, INIntentHandlingStatus.inProgress,INIntentHandlingStatus.ready:
chatViewController.isSent = false
case INIntentHandlingStatus.done:
chatViewController.isSent = true
}
present(chatViewController, animated: false, completion: nil)
size = desiredSize
}
else {
// Otherwise, we'll tell the host to draw us at zero size.
size = CGSize.zero
}
completion(size)
}
- Embedded frameworks
由于你的程序是extension,因此你的siri擴展可能會和你的應用有數據共享,而且可能會有同樣的邏輯代碼,因此apple建議相關業務的邏輯代碼可用framwork的形式,這樣擴展和宿主app都可以使用。