一、苹果官方提供的Siri开发方法
1、iOS4 - iOS9:不开放Siri API
Siri助手首次出现在iOS4中,但是一直到iOS10之前,苹果都不开放Siri的接口,所以用户使用Siri时,只能使用苹果官方提供的服务,如:
1、帮我设定一个明天早上7点的闹钟
2、打电话给妈妈
3、20分钟后提醒我抢火车票
2、iOS10: SiriKit
在WWDC2016,iOS10版本中,苹果开放了Siri的API,开发者使用SiriKit也可以将自己APP的服务提供给用户,例如:
1、用QQ音乐播放谭咏麟的歌
2、微信发消息给小梦,说我10分钟后到达
命令中需要指定域(Domain,可理解为任务的类型)下的指定意图(Intent,可理解为具体任务),苹果提供的意图如下:
1、语音通话(VoIP Calling):打电话、查通话记录
2、信息 (Messaging):发信息、搜索信息
3、媒体(Media):播放或控制音频
4、任务或笔记(Lists and Notes) :创建或管理任务或笔记
5、付款(Payments):给用户付款或结算账单
6、餐馆预定(Restaurant Reservations):通过地图应用预定餐馆或管理预定
7、健身( Workouts):开始、暂停、管理健身
8、打车 (Ride Booking):查看附近可用的车辆、订车、查看订单
9、控制汽车(Car Commands):控制门锁、获取车辆状态
例如用微信发送消息给小梦,说我10分钟后到达,解析后为:
域(Domain):信息 (Messaging)
意图(Intent):发信息(INSendMessageIntent)
意图参数 (Intent Parameter):
收件人(recipients):小梦
消息内容(content):我10分钟后到达
随着iOS版本的迭代,可能支持更多意图,可以查看最新的官方文档
3、iOS12: Siri ShortCut
在WWDC 2018,苹果进一步开放了Siri功能,推出了Siri Shortcuts,用户只需一句预先设定的短语,即可触发指定的操作,如
1、播放下雨声 (触发小睡眠APP播放下雨声的白噪音)
2、咖啡时间 (触发某咖啡APP下单一杯咖啡)
二、SiriKit示例
因为SiriKit和Siri ShortCut开发代码逻辑存在一定差异,为防止文章过长,这一篇先介绍使用SiriKit发送消息的实现。
当用户通过Siri给好友发送消息时,系统会识别为消息域中的发送消息意图,并创建一个 INSendMessageIntent,包含消息接受者、消息内容等参数,然后调用 app extension 的 handler,解析上下文,确认可以处理并发送完消息后返回 INSendMessageIntentResponse 类型的结果。下面我们一步步来实现
1、创建Demo项目:
打开Xcode,菜单选择File->New->Project,创建名为MySiriKitDemo的新项目
2、添加Intent Extension
菜单选择File->New->Target,选择Intents Extension,点Next,命名为MySirExtension,且勾选Include UI Extension,以便后面添加交互UI
3、配置
如图修改项目名称为聊天测试,或其它你喜欢的名称。Demo完成后,我们使用siri发送消息时,需要用到这个名称,如用“聊天测试发送消息给小梦告诉他我到了”。
打开MySirExtension下的Info.plist文件,创建Intents Extension时默认即为Message Intents,所以IntentsSupported下的项目不需要做修改(如果只用到发送消息,也可只保留INSendMessageIntent)。
如果是媒体播放,需更改为INPlayMediaIntent,如果是健身,需更改为INStartWorkoutIntent、INPauseWorkoutIntent、INResumeWorkoutIntent、INCancelWorkoutIntent、INEndWorkoutIntent等,具体可参考官方文档
4、共享的用户信息
在开始正式处理Siri数据前,我们先在主项目新建文件MyAccount.swift,并创建用户相关类,包括好友类MyUser和账号管理类MyAccount,记得同时添加到主项目及Extension中。
- MyUser:好友对象 ,包含好友的昵称信息
- MyAccount:账号管理类,单例,contact方法传入好友昵称返回好友对象,send方法为给指定好友发送消息
MyAccount.swift完整代码:
import Intents
class MyUser {
var name:String?
var handle: String?
init(name: String? = nil, handle: String? = nil) {
self.name = name
self.handle = handle
}
func toInPerson() -> INPerson {
return INPerson(handle: handle!, displayName: name, contactIdentifier: name)
}
}
class MyAccount {
private static let instance = MyAccount()
class func share() -> MyAccount {
return MyAccount.instance
}
func contact(matchingName: String) -> [MyUser] {
return [MyUser(name: matchingName, handle: NSStringFromClass(MySendMessageIntentHandler.classForCoder()))]
}
func send(message: String,to recipients:[INPerson]) -> INSendMessageIntentResponseCode {
print("模拟发送消息:\(message) 给 \(recipients)")
return .success
}
}
这里假设了用户通过Siri所呼叫的好友真实存在,即MyAccount的contact方法永远能返回昵称对应的好友,具体实现要根据项目原有的账号逻辑。
5、处理Siri数据
打开IntentHandler.swift,苹果已经为我们生成了消息的示例代码,虽然可以在这基础上修改,但我们这里只实现发消息的效果,防止过多冗余代码,我们删除之。这里我们只需实现INSendMessageIntentHandling协议下的三个阶段方法:
- Resolve:处理阶段,解析Siri数据(处理收件人、处理发送的文字内容,这里使用了上一步的好友类、账号类进行匹配)
- Confirm:确认阶段,可以判断用户是否有权限(如是否已登录)、可以获取intent事件中的值,并可对其做最终修改
- Handle: 处理阶段,实现app消息发送的代码逻辑
这里我们创建MySendMessageIntentHandler.swift类,实现INSendMessageIntentHandling中的这几个方法:
import UIKit
import Intents
class MySendMessageIntentHandler: NSObject,INSendMessageIntentHandling {
// MARK: - INSendMessageIntentHandling
// Implement resolution methods to provide additional information about your intent (optional).
// 处理收件人
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
if let recipients = intent.recipients {
// If no recipients were provided we'll need to prompt for a value.
if recipients.count == 0 {
completion([INSendMessageRecipientResolutionResult.needsValue()])
return
}
var resolutionResults = [INSendMessageRecipientResolutionResult]()
for recipient in recipients {
let matchingContacts = MyAccount.share().contact(matchingName: recipient.displayName)
// Implement your contact matching logic here to create an array of matching contacts
switch matchingContacts.count {
case 2 ... Int.max:
// We need Siri's help to ask user to pick one from the matches.
let disambiguations = matchingContacts.map{ $0.toInPerson() }
resolutionResults += [INSendMessageRecipientResolutionResult.disambiguation(with: disambiguations)]
case 1:
// We have exactly one matching contact
let recipient = matchingContacts[0].toInPerson()
resolutionResults += [INSendMessageRecipientResolutionResult.success(with: recipient)]
case 0:
// We have no contacts matching the description provided
resolutionResults += [INSendMessageRecipientResolutionResult.unsupported()]
default:
break
}
}
completion(resolutionResults)
} else {
completion([INSendMessageRecipientResolutionResult.needsValue()])
}
}
// 处理发送的文字内容
func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
if let text = intent.content, !text.isEmpty {
completion(INStringResolutionResult.success(with: text))
} else {
completion(INStringResolutionResult.needsValue())
}
}
// Once resolution is completed, perform validation on the intent and provide confirmation (optional).
// 确认阶段,可以判断用户是否有权限(如是否已登录)、可以获取intent事件中的值,并可对其做最终修改
func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
// Verify user is authenticated and your app is ready to send a message.
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity)
completion(response)
}
// Handle the completed intent (required).
// 处理阶段,实现app消息发送的代码逻辑
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
// Implement your application logic to send a message here.
if let content = intent.content,let recipients = intent.recipients {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
let sendResult = MyAccount.share().send(message: content, to: recipients)
completion(INSendMessageIntentResponse(code: sendResult, userActivity: userActivity))
}else {
let response = INSendMessageIntentResponse(code: .failure, userActivity: nil)
completion(response)
}
}
}
最后在IntentHandler中,返回MySendMessageIntentHandler对象:
import Intents
// As an example, this class is set up to handle Message intents.
// You will want to replace this or add other intents as appropriate.
// The intents you wish to handle must be declared in the extension's Info.plist.
// You can test your example integration by saying things to Siri like:
// "Send a message using <myApp>"
// "<myApp> John saying hello"
// "Search for messages in <myApp>" INSendMessageIntentHandling
class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any? {
if intent is INSendMessageIntent {
return MySendMessageIntentHandler()
}
return nil
}
}
6、运行测试
对Siri说用聊天测试发消息给小梦说我马上到,运行效果如下:
完整代码:下载