siri 快捷方式按钮 (INUIAddVoiceShortcutButton) 在有多个快捷方式 (NSUserActivity) 时显示错误的标题

siri shortcut button (INUIAddVoiceShortcutButton) shows wrong title when have multiple shortcuts (NSUserActivity)

我的应用程序中有 2 个 siri 快捷方式。 我使用 NSUserActivity 来提供这些快捷方式。我还在 info.plist 中创建了 2 个 NSUserActivityType。

有 2 个视图控制器处理这些快捷方式(1 个视图控制器用于 1 个快捷方式)。

如果我从 1 个视图控制器添加 1 个 siri 快捷方式,然后转到第 2 个视图控制器,第 2 个视图控制器上的本机 siri 快捷方式按钮 (INUIAddVoiceShortcutButton) 会自动选择第一个快捷方式(从第 1 个视图控制器创建)并显示带有建议短语的 "Added to Siri" 而不是显示 "Add to Siri" 按钮。我仔细检查了每个 NSUserActivity 都有不同的标识符,但仍然以某种方式选择了错误的快捷方式。

视图控制器 1:

let userActivity = NSUserActivity(activityType: "com.activity.type1")
userActivity.isEligibleForSearch = true
userActivity.isEligibleForPrediction = true
userActivity.title = shortcut.title
userActivity.suggestedInvocationPhrase = suggestedPhrase

let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributes.contentDescription = description
userActivity.contentAttributeSet = attributes
let shortcut = INShortcut(userActivity: userActivity)
let siriButton = INUIAddVoiceShortcutButton(style: .whiteOutline)
siriButton.translatesAutoresizingMaskIntoConstraints = false
siriButton.shortcut = shortcut
self.view.addSubview(siriButton)

视图控制器 2:

let userActivity2 = NSUserActivity(activityType: "com.activity.type2")
userActivity2.isEligibleForSearch = true
userActivity2.isEligibleForPrediction = true
userActivity2.title = shortcut.title
userActivity2.suggestedInvocationPhrase = suggestedPhrase

let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributes.contentDescription = description
userActivity2.contentAttributeSet = attributes

let shortcut = INShortcut(userActivity: userActivity2)
let siriButton = INUIAddVoiceShortcutButton(style: .whiteOutline)
siriButton.translatesAutoresizingMaskIntoConstraints = false
siriButton.shortcut = shortcut
self.view.addSubview(siriButton)

当我删除应用程序并重新安装而不删除 Phone 的设置应用程序中的快捷方式时,会发生类似的事情。

似乎是一个 IOS 错误。我想出了解决这个问题的方法。每次用户 add/edit siri 快捷方式时,您都必须创建一个新的 siri 按钮。在创建 siri 按钮之前做以下事情

1- 通过调用该函数从 INVoiceShortcutCenter 获取所有语音快捷方式。请注意,这是异步发生的,因此您需要在需要数据之前的某个时间执行此操作(例如,在您的 AppDelegate 中)。每当用户添加 Siri 快捷方式(可能在 INUIAddVoiceShortcutViewControllerDelegate.addVoiceShortcutViewController(_:didFinishWith:error) 方法中)时,您还需要重新加载它。

INVoiceShortcutCenter.shared.getAllVoiceShortcuts  { (voiceShortcutsFromCenter, error) in
    guard let voiceShortcutsFromCenter = voiceShortcutsFromCenter else {
            if let error = error as NSError? {
                os_log("Failed to fetch voice shortcuts with error: %@", log: OSLog.default, type: .error, error)
            }
            return
        }
        self.voiceShortcuts = voiceShortcutsFromCenter
}

2- 在 View Controller-1 中通过迭代所有语音快捷方式检查是否已添加快捷方式

let voiceShorcut = voiceShortcuts.first { (voiceShortcut) -> Bool in
    if let activity = voiceShortcut.shortcut.userActivity, activity.activityType == "com.activity.type1" {
        return true
    }
    return false
}

3- 如果您的语音快捷方式已注册,则将 INShortcut 传递给 siri 按钮,否则不要设置它。

if voiceShorcut != nil {
    let shortcut = INShortcut(userActivity: userActivity1)
    siriButton.shortcut = shortcut
} 

在第二个视图控制器中做同样的事情。

我只是通过将我的实现(最初基于 soupchef 应用程序)更改为 apple (https://developer.apple.com/documentation/sirikit/inuiaddvoiceshortcutbutton) 提供的代码示例来自己解决了这个问题:

编辑:我添加了代码,展示了我如何为 UserActivity 和自定义 Intent 快捷方式创建和传递 shortcutObject (INShortcut)。

快捷方式 class 是一个枚举,它包含一个计算的 属性 调用的意图,returns 自定义意图的实例化。

private func addShortcutButton(shortcut: Shortcut, parentViewController: UIViewController, shortcutViewControllerDelegate: INUIAddVoiceShortcutViewControllerDelegate) {
    guard let view = parentViewController.view else { return }

    if let intent = shortcut.intent {
        shortcutObject = INShortcut(intent: intent)
    } else if let userActivity = view.userActivity {
        shortcutObject = INShortcut(userActivity: userActivity)
    }

    self.shortcutViewControllerDelegate = shortcutViewControllerDelegate
    addSiriButton(to: shortcutButtonContainer)
}

func addSiriButton(to view: UIView) {
    let button = INUIAddVoiceShortcutButton(style: .whiteOutline)
    button.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(button)
    view.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
    view.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true

    button.addTarget(self, action: #selector(addToSiri(_:)), for: .touchUpInside)
}

// Present the Add Shortcut view controller after the
// user taps the "Add to Siri" button.
@objc
func addToSiri(_ sender: Any) {
    guard let shortcutObject = shortcutObject else { return }
    let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcutObject)
    viewController.modalPresentationStyle = .formSheet
    viewController.delegate = shortcutViewControllerDelegate
    parentViewController?.present(viewController, animated: true, completion: nil)
}

这是 iOS 12.0 错误。 您可以通过使用正确的值更新 INUIAddVoiceShortcutButton.voiceShortcut 来修复它。 使用 KVO 观察 "voiceShortcut" 属性 并在它发生变化时为其分配正确的值。

我现在已经转到意图设置,我发现即使只有一个意图设置并使用 INUIAddVoiceShortcutButton 也无法跟踪我的快捷方式。记录短语后,它会显示用短语添加到 Siri。

但每次应用程序重新启动时,都会显示“添加到 Siri”按钮,而不是“添加到 Siri”按钮和录制的短语。

我尝试按照 Bilal 的建议进行操作,虽然我可以看到 INVoiceShortcutCenter 显示我的快捷方式,但它没有将它加载到 Siri 按钮中。

对于按钮本身,我的代码看起来像这样。

 private func addSiriButton() {
    let addShortcutButton = INUIAddVoiceShortcutButton(style: .blackOutline)
    addShortcutButton.delegate = self

    addShortcutButton.shortcut = INShortcut(intent: engine.intent )
    addShortcutButton.translatesAutoresizingMaskIntoConstraints = false

    siriButtonSubView.addSubview(addShortcutButton)
    siriButtonSubView.centerXAnchor.constraint(equalTo: addShortcutButton.centerXAnchor).isActive = true
    siriButtonSubView.centerYAnchor.constraint(equalTo: addShortcutButton.centerYAnchor).isActive = true

}

我已实施所有协议,并且仔细查看了 Soup 应用程序,但就是无法弄清楚是什么导致了这种不准确。

有趣的是,甚至英国航空公司的应用程序开发人员也放弃了它,因为他们的按钮具有完全相同的故障行为。

更新:我已经构建了另一个测试项目,其中 Intent 的实现量最少,添加到 Siri 和添加到 Siri 的效果非常好。我猜此时我自己的应用程序代码库中有某些东西导致了这种不需要的行为。

更新 2 只是想让大家知道我已经解决了这个问题。使用意图很好,但意图定义文件本身肯定有一点敏感性。我所要做的就是创建一个新的意图,然后生成并起作用。似乎我最初的意图在某种程度上是腐败的,但没有错误。在创建另一个意图并为其重新分配意图处理功能后,它都按预期工作。 (双关语)

所以我们不能使用默认的Siri按钮,你必须使用自定义的UIButton。 class VoiceShortcutsManager 将检查所有语音意图,然后我们可以搜索该列表检查是否存在一个匹配项,如果是,那么我们应该建议版本,如果不存在,我们应该建议添加。

public class VoiceShortcutsManager {

    private var voiceShortcuts: [INVoiceShortcut] = []

    public init() {

        updateVoiceShortcuts(completion: nil)
    }


    public func voiceShortcut(for order: DeviceIntent, powerState: State) -> INVoiceShortcut? {

        for element in voiceShortcuts {

            guard let intent = element.shortcut.intent as? ToggleStateIntent else {
                continue
            }
            let deviceIntent = DeviceIntent(identifier: intent.device?.identifier, display: intent.device?.displayString ?? "")
            if(order == deviceIntent && powerState == intent.state) {
                return element
            }
        }
        return nil
    }


    public func updateVoiceShortcuts(completion: (() -> Void)?) {

        INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (voiceShortcutsFromCenter, error) in
            guard let voiceShortcutsFromCenter = voiceShortcutsFromCenter else {
                if let error = error {
                    print("Failed to fetch voice shortcuts with error: \(error.localizedDescription)")
                }
                return
            }
            self.voiceShortcuts = voiceShortcutsFromCenter
            if let completion = completion {
                completion()
            }
        }
    }

}

然后在你的 ViewController

中实施
class SiriAddViewController: ViewController {

    let voiceShortcutManager = VoiceShortcutsManager.init()

    override func viewDidLoad() {
        super.viewDidLoad()

        contentView.btnTest.addTarget(self, action: #selector(self.testBtn), for: .touchUpInside)
    }

    ...


    @objc func testBtn() {

        let deviceIntent = DeviceIntent(identifier: smartPlug.deviceID, display: smartPlug.alias)

        //is action already has a shortcut, update shortcut else create shortcut
        if let shortcut = voiceShortcutManager.voiceShortcut(for: deviceIntent, powerState: .off) {

            let editVoiceShortcutViewController = INUIEditVoiceShortcutViewController(voiceShortcut: shortcut)
            editVoiceShortcutViewController.delegate = self
            present(editVoiceShortcutViewController, animated: true, completion: nil)
        } else if let shortcut = INShortcut(intent: intentTurnOff) {

            let addVoiceShortcutVC = INUIAddVoiceShortcutViewController(shortcut: shortcut)
            addVoiceShortcutVC.delegate = self
            present(addVoiceShortcutVC, animated: true, completion: nil)
        }
    }

}


@available(iOS 12.0, *)
extension SiriAddViewController: INUIAddVoiceShortcutButtonDelegate {


    func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {
        addVoiceShortcutViewController.delegate = self
        addVoiceShortcutViewController.modalPresentationStyle = .formSheet
        present(addVoiceShortcutViewController, animated: true, completion: nil)
    }


    func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {

        editVoiceShortcutViewController.delegate = self
        editVoiceShortcutViewController.modalPresentationStyle = .formSheet
        present(editVoiceShortcutViewController, animated: true, completion: nil)
    }

}


@available(iOS 12.0, *)
extension SiriAddViewController: INUIAddVoiceShortcutViewControllerDelegate {

    func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {

        voiceShortcutManager.updateVoiceShortcuts(completion: nil)
        controller.dismiss(animated: true, completion: nil)
    }

    func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
        controller.dismiss(animated: true, completion: nil)
    }

}


@available(iOS 12.0, *)
extension SiriAddViewController: INUIEditVoiceShortcutViewControllerDelegate {


    func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) {

        voiceShortcutManager.updateVoiceShortcuts(completion: nil)
        controller.dismiss(animated: true, completion: nil)
    }

    func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) {

        voiceShortcutManager.updateVoiceShortcuts(completion: nil)
        controller.dismiss(animated: true, completion: nil)
    }

    func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) {

        voiceShortcutManager.updateVoiceShortcuts(completion: nil)
        controller.dismiss(animated: true, completion: nil)
    }

}
}

此代码来自 inspired/copy 此网页: https://www.nodesagency.com/test-drive-a-siri-shortcuts-intro/

我解决这个问题的经验有点不同。通过添加到 Siri 按钮添加的一些意图有效,调整为“添加到 Siri”,而其他则没有。我意识到有效的操作不需要参数。

在为公开参数的 Intent 设置默认值后,这些参数被传递到 INShortcut(然后分配给 INUIAddVoiceShortcutButton),所有按钮正确更新了他们的状态!

当我有一个现有的意图和工作配置,但添加了一个新参数时,我遇到了这个错误。但是,在我的 Intent 配置中,我没有将新参数 name 添加到 Shortcuts app 部分下的受支持组合。

例如,如果我有两个属性 myIdmyName,并将它们指定为:

let intent = MyIntent()
intent.myId = 1234
intent.myName = "banana"

然后我需要在我的意图定义文件中支持 myId, myName 的组合。在我的特殊情况下,我忘记了 myName 所以 INUIAddVoiceShortcutButton 试图使用 myId, myName 进行查找但不知道如何。