iOS 10 来了,Siri也有了大变化,可以让Siri帮我们发消息,开始锻炼,具体有如下六类服务:
- 语音和视频通话
- 发送消息
- 收款或者付款
- 图片搜索
- 管理锻炼
- 行程预约
下面开始看看怎么撩Siri
创建 Intents Extension
与SiriKit
的互动是通过Intents Extension
实现的,我们需要创建一个Intents Extension
来与Siri
互动并响应操作,所以,在此之前你必须有一个app
,或者新建一个项目
为已有项目添加Intents Extension
,有以下几点:
- 把项目
Capability
的Siri
启用;见Xcode项目设置 - 在项目中添加一个
Intents Extension
,并且在Info.plist
中进行配置;见Xcode项目设置 - 请求
Siri
权限;见请求Siri权限 - 定义一个处理
intents
的对象;见处理Intents 定义一些自定义的词汇;见自定义词汇
注:
你可以在项目中添加一个Intents UI extension
来自定义Siri
或者地图
的显示界面,Intents UI extension
不能代替Intents Extension
,两者是不同的东西,有关Intents UI Extension
,见创建Intents UI Extension
Xcode项目设置
除了添加一个Intents extension target
到你的项目中,你还需要做一些设置
要使用SiriKit
,你必须启用Siri capability
, 就像启用iCloud
,push notifications(通知)
,in-app purchase(应用内购买)
,让App Store知道你的app支持Siri
在app中启用Siri功能
- 在Xcode中打开项目
- 在项目设置中,选中app的target
- 切换到Capabilities这一项
- 找到Siri这一项,启用
在项目中新增一个Intents extension target
- 在Xcode中打开项目
- 选择 File > New > Target
- 选择 Intents extension
- 点击下一步
- 起一个名字,和工程名差不多就好了,如果你打算自定义Siri的UI界面,把Include UI Extension钩上
点击完成
注:
你可以添加多个 Intents extension,但每个 Intents extension必须是支持不同的Intents,除非是有性能优势或者减少了内存占用,否则不建议这么做
指定扩展(extension)支持的Intents
- 在Xcode中,选择新创建的
Intents extension
中的Info.plist
文件 - 点击展开
NSExtension
,然后展开NSExtensionAttributes
,可以看到有IntentsSupported
和IntentsRestrictedWhileLocked
这两个键 - 在
IntentsSupported
下,每一个字符串表示的是应用扩展处理的Intent
事件的类名,我们需要为每个扩展都添加一行(这个必须设置) - 如果需要在锁屏时禁用某个功能是锁屏情况下,则再在IntentsRestrictedWhileLocked中加入相应项的
Intent
(这个为可选)
当用户说出了一些语义相似的话,也许会启用多个扩展,这时候,会根据在IntentsSupported下面的数组的顺序决定哪个扩展先响应,所以,当你有一个Intents非常重要需要先响应,你需要将它在数组中靠前排列
请求Siri权限
用户必须授予应用程序的权限使用SiriKit。要请求您的应用程序的权限,如下:
- 在项目的
Info.plist
加入一行,键为NSSiriUsageDescription
,值为请求访问Siri
权限时,弹出框的文字,例如:允许app访问Siri以完成更多操作 - 调用
INPreferences
类的requestSiriAuthorization:
方法请求授权
当你第一次调用 requestSiriAuthorization
的时候,系统将弹出一个请求授权的对话框让用户授权你的app
,对话框的描述内容就是你在Info.plist
文件中的NSSiriUsageDescription
对应的值,用户可以批准或者拒绝你的请求授权,随后也可以在系统设置里面修改授权状态,当你再次调用requestSiriAuthorization
时,系统会记住用户之前的授权状态而不会再次提示用户进行授权
测试extension
先把我们自己的app
跑起来,然后运行扩展(extension
),会看到如下窗口:
选择我们自己的
app
,开始run,在run起来的程序中请求授权:处理Intents
当我们实现了Intents extension扩展并产生了一个Siri请求事件时,一个典型的Intent事件的处理过程中总共有这三个步骤Resolve
、Confirm
和Handle
:
- Resolve阶段。在Siri获取到用户的语音输入之后,生成一个INIntent对象,将语音中的关键信息提取出来并且填充对应的属性。这个对象在稍后会传递给我们设置好的INExtension子类对象进行处理,根据子类遵循的不同服务protocol来选择不同的解决方案
- Confirm阶段。在上一个阶段通过handler(for intent:)返回了处理intent的对象,此阶段会依次调用confirm打头的实例方法来判断Siri填充的信息是否完成。匹配的判断结果包括Exactly one match、Two or more matches以及No match三种情况。这个过程中可以让Siri向用户征求更具体的参数信息
- 在confirm方法执行完成之后,Siri进行最后的处理阶段,生成答复对象,并且向此intent对象确认处理结果然后执显示结果给用户看
Resolve
这个阶段需要我们找到消息的具体接收者。在这个过程中,可能会出现三种情况:Exactly one match、Two or more matches以及No matches,对于这三种情况的处理,代码如下:
func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
if let recipients = intent.recipients {
// If no recipients were provided we'll need to prompt for a value.
if recipients.count == 0 {
completion([INPersonResolutionResult.needsValue()])
return
}
var resolutionResults = [INPersonResolutionResult]()
for recipient in recipients {
var matchingContacts : [INPerson] = []
//1
contacts.forEach({ (contact) in
if contact.hasPrefix(recipient.displayName) {
let handle = INPersonHandle.init(value: contact + "test", type:.unknown)
matchingContacts.append(INPerson.init(personHandle: handle, nameComponents:nil, displayName: contact, image: nil, contactIdentifier: nil, customIdentifier: nil))
}
})
switch matchingContacts.count {
case 2 ... Int.max:
// We need Siri's help to ask user to pick one from the matches.
resolutionResults += [INPersonResolutionResult.disambiguation(with: matchingContacts)]
case 1:
// We have exactly one matching contact
resolutionResults += [INPersonResolutionResult.success(with: recipient)]
case 0:
// We have no contacts matching the description provided
resolutionResults += [INPersonResolutionResult.unsupported()]
default:
break
}
}
completion(resolutionResults)
}
}
1.此处是程序根据Siri
提供的接收人信息,筛选出相应的收信人。例如:我们的通讯录有陈经理
,陈小哥
,胖子
,我们对Siri
说,用小马(我们的app名)
发消息给姓陈的,因为此处有两位姓陈的,所以我们需要将两个都匹配出来,放入matchingContacts
中,使用INPersonResolutionResult.disambiguation
,让Siri
询问用户具体是哪一位姓陈的
下面是匹配消息内容:
func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
if let text = intent.content, !text.isEmpty {
completion(INStringResolutionResult.success(with: text))
} else {
completion(INStringResolutionResult.needsValue())
}
}
Confirm
在该阶段,我们检测用户的登录状态,以决定是否发送消息,在这个阶段,你可以最后一次修改Intent
内容,未登录时,我们传入failureRequiringAppLaunch
启动app
进行相应的登录操作:
func confirm(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
if hasLogin() {
completion(INSendMessageIntentResponse(code: .success, userActivity: nil))
}else{
// Creating our own user activity to include error information.
let userActivity = NSUserActivity(activityType: String(describing: INSendMessageIntent.self))
userActivity.userInfo = [NSString(string: "error"):NSString(string: "UserLoggedOut")]
completion(INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity))
}
}
Handle
到了这个阶段,检查下各项信息是否为空,调用api去发送消息,有必要的时候保存数据,然后反馈任务状态给Siri
:
func handle(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
if intent.recipients != nil && intent.content != nil {
// Send the message.
let success = sendMessage(content: intent.content!,contacts: intent.recipients!)
completion(INSendMessageIntentResponse(code: success ? .success : .failure, userActivity: nil))
}
else {
completion(INSendMessageIntentResponse(code: .failure, userActivity: nil))
}
}
运行之后:
创建Intents UI Extension
上一步创建Intents Extension
的时候我们已经创建了Intents UI Extension
了,这里,我们可以改变展示的ui
,在Storyboard
里面加入一个label
运行:
参考文章:
SiriKit Programming Guide
iOS开发——SiriKit应用
SiriKit 初探 —— WWDC 2016 技术赏析