引言
- 在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都可以使用。
网友评论