最近项目准备加入捷径功能,今天下午抽时间把功能做了,记录一下遇到的问题和一些思路。
需要做的功能是根据多个分类添加多条捷径(分类个数和内容不可知),提示内容跟随分类修改,如果已经添加,则进行编辑操作。通过捷径唤醒应用时找到对应的分类。
-
先在项目TARGETS的info部分加入键值对
Privacy - Siri Usage Description,value为使用Siri的描述信息
-
NSUserActivityTypes,value为数组,item为字符串,作为 NSUserActivity的activityType,用于判断应用被Siri唤醒的条件。
-
在项目TARGETS的Capabilities部分配置Siri环境
到这里准备工作已经做好了,接下来就是在使用的时候进行授权
if #available(iOS 10.0, *) {
guard INPreferences.siriAuthorizationStatus() == .notDetermined else {
return
}
INPreferences.requestSiriAuthorization { (status) in}
} else {
// Fallback on earlier versions
}
补充一些需要导入的头文件
import Intents /// INVoiceShortcut
import IntentsUI /// INUIEditVoiceShortcutViewController INUIAddVoiceShortcutViewController
import CoreSpotlight /// CSSearchableItemAttributeSet
- 获得已经添加的INVoiceShortcut,用来判断是继续添加还是进行修改
INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (shortcuts, error) in
guard let shortcuts = shortcuts,shortcuts.count > 0 else {
return
}
self.shortcuts = shortcuts
}
- 先来创建一个userActivity用于添加捷径
/// 创建一个用户活动
///
/// - Parameters:
/// - title: 标题
/// - phrase: 短语
/// - description: 描述
private func initUserActivity(identifier: String, title: String? = nil, phrase: String? = nil, description: String? = nil) -> NSUserActivity {
let userActivity = NSUserActivity.init(activityType: "make1.com.Siri.location") /// 根据plist文件的值,创建 UserActivity,用于判断和区分捷径
userActivity.isEligibleForSearch = true /// 设置 YES,通过系统的搜索,可以搜索到该 UserActivity
if #available(iOS 12.0, *) {
userActivity.isEligibleForPrediction = true /// 允许系统预测用户行为,并在合适的时候给出提醒。(搜索界面,屏锁界面等。)
userActivity.suggestedInvocationPhrase = phrase /// 设置引导用户设置捷径的短语
userActivity.title = title ///标题
userActivity.userInfo = ["identifier":identifier] /// 用于辅助判断和区分捷径,也可以传输内容
let set = CSSearchableItemAttributeSet()
set.contentDescription = description /// 设置对捷径功能的辅助描述
userActivity.contentAttributeSet = set
userActivity.becomeCurrent()
return userActivity
}
- 在添加捷径的时候先进行判断是否已经添加,可以通过userActivity的activityType,也可以通过userActivity的userInfo来进去区分
@available(iOS 12.0, *)
/// 创建一个捷径控制器
///
/// - Parameters:
/// - identifier: 唯一标识符,用于判断是创建捷径还是修改捷径
/// - title: 标题
/// - phrase: 短语
/// - description: 描述
/// - Returns: 捷径控制器
func present(identifier: String, title: String? = nil, phrase: String? = nil, description: String? = nil) -> UIViewController {
var voiceShortcutController: UIViewController!
if let shortcut = shortcuts.filter({(($0 as? INVoiceShortcut)?.shortcut.userActivity?.userInfo?["identifier"] as? String) == identifier}).first as? INVoiceShortcut {
voiceShortcutController = INUIEditVoiceShortcutViewController(voiceShortcut: shortcut)
voiceShortcutController.delegate = self
}else {
let shortCut = INShortcut.init(userActivity: initUserActivity(identifier: identifier, title: title, phrase: phrase, description: description))
voiceShortcutController = INUIAddVoiceShortcutViewController.init(shortcut: shortCut)
voiceShortcutController.delegate = self
}
return voiceShortcutController
}
- 设置捷径的代理,可根据实际情况设置代理,我是封装了整个捷径模块,自己处理事件
@available(iOS 12.0, *)
extension GShortcutManager : INUIAddVoiceShortcutViewControllerDelegate {
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
KEY_WINDOW()?.rootViewController?.dismiss(animated: true, completion: nil)
}
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {
if let voiceShortcut = voiceShortcut{
shortcuts.append(voiceShortcut)
}
KEY_WINDOW()?.rootViewController?.dismiss(animated: true, completion: nil)
}
}
@available(iOS 12.0, *)
extension GShortcutManager: INUIEditVoiceShortcutViewControllerDelegate {
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) {
KEY_WINDOW()?.rootViewController?.dismiss(animated: true, completion: nil)
}
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) {
KEY_WINDOW()?.rootViewController?.dismiss(animated: true, completion: nil)
shortcuts.removeAll(where: {($0 as? INVoiceShortcut)?.identifier == deletedVoiceShortcutIdentifier})
}
func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) {
KEY_WINDOW()?.rootViewController?.dismiss(animated: true, completion: nil)
}
}
- 设置捷径的时候直接加载ShortcutViewController即可
- Siri唤醒App的时候会调用AppDelegate的代理事件
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
restorationHandler(nil)
if #available(iOS 12.0, *) {
if userActivity.activityType == "make1.com.Siri.location" { ///plist文件的值
guard let identifier = userActivity.userInfo?["identifier"] as? String else {
return true
}
/// 用identifier来区分不同的分类来做不同的处理
}
}
return false
}
大功告成,捷径的简单应用就这些了,接下来会抽时间来写Siri的其他功能,应用外的一些处理。欢迎一起讨论。