当有多个快捷方式 (NSUserActivity) 时,Siri快捷按钮 (INUIAddVoiceShortcutButton) 显示错误的标题。

9
我在我的应用程序中有两个Siri快捷方式。 我使用NSUserActivity来捐赠这些快捷方式。我还在info.plist中创建了2个NSUserActivityTypes。
有两个视图控制器处理这些快捷方式(1个视图控制器处理1个快捷方式)。
如果我从一个视图控制器添加一个Siri快捷方式,然后转到第二个视图控制器,第二个视图控制器上的本机Siri快捷方式按钮(INUIAddVoiceShortcutButton)会自动选择第一个快捷方式(从第一个视图控制器创建),并显示“已添加到Siri”与建议的短语,而不是显示“添加到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)

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

3
似乎是IOS的一个bug。我发现了一个解决这个问题的方法。每次用户添加/编辑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- 在视图控制器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
} 

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

2

这是iOS 12.0的一个bug。 您可以通过使用正确的值更新INUIAddVoiceShortcutButton.voiceShortcut来修复它。 使用KVO观察“voiceShortcut”属性,当它改变时将正确的值分配给它即可。


1
我在INUIAddVoiceShortcutButton上没有看到voiceShortcut属性 - 这是私有的吗? - Tim
1
好的,甚至没有观察,我只需调用[addToSiriButton setValue:voiceShortcut forKey:@"voiceShortcut"]就可以修复它了,在那里我曾经调用addToSiriButton.shortcut = voiceShortcut.shortcut; - Tim
1
@Vitaliy Alekseev,您能更好地解释一下吗? - Tiago Mendes

0

当我已经有一个存在的意图和工作配置,但添加了一个新参数时,我遇到了这个错误。然而,在我的意图配置中,我没有将新参数name添加到快捷方式应用程序部分的支持组合中。

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

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

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


0

我现在已经转移到意图设置,即使只设置一个意图并且使用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应用程序,但是就是想不通是什么导致了这种不准确性。

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

更新:我建立了另一个测试项目,并进行了最少量的意图和添加到Siri的实现,这两个功能都可以完美地工作。我猜想此时我的自己应用程序代码库中有一些东西导致了这种不希望的行为。

更新2:我只是想让大家知道我已经解决了这个问题。使用意图很好用,但意图定义文件本身确实有点敏感。我所要做的就是创建一个新意图,然后生成它,然后就可以正常工作了。似乎我的初始意图有点损坏,但没有错误。在创建另一个意图并将意图处理函数重新分配给它之后,它全部按预期运行了。(意图达成)


有没有办法在“已添加到Siri”下面不显示短语?我看到一些应用程序这样做,但我自己无法弄清楚。 - Rumin
嗨Rumin,我还没有找到删除下面文本的方法。文本本身只有在短语被记录到服务器后才会添加,作为提醒您有一个短语的方式,这是苹果建议使用简短易记的短语的原因之一。另一个原因是显示文本的整个“添加到Siri”按钮是可本地化的,因此您可以将“添加到Siri”和记录的短语本地化。 - Dan Korkelia
刚刚又仔细看了一下INUIAddVoiceShortcutButton,它只是UIButton的一个子类,这意味着你可以将标签设置为任何你想要的,尽管我还不确定是否百分之百正确。实际上,INUIAddVoiceShortcutButtonStyle是设置按钮样式的Init的一部分,它只是一个{ get }属性。 - Dan Korkelia
是的,谢谢你,丹!看起来“添加到Siri”文本是在设置短语后由苹果默认实现的。如果我尝试设置标题,它会重叠该文本。 - Rumin

-1

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

编辑:我添加了显示如何创建和传递UserActivity和自定义意图快捷方式的shortcutObject(INShortcut)的代码。

Shortcut类是一个包含计算属性intent的枚举,该属性返回自定义意图的实例化。

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)
}

1
问题出在多个快捷键上。你在哪里处理多个快捷键? - Bilal
我有一个属性,用于设置我想要使用的快捷方式的用户活动,并将其用于创建INShortcut。等我回到我的开发机器上,我会更新更详细的代码。 - rmooney
嘿,我目前正在与这个设置作斗争。你能详细说明一下如何使用苹果提供的UserActivity示例吗?如果让快捷方式= INShortcut(userAcivity:在这里使用函数选择活动吗? - Dan Korkelia
丹,我传递了包含捐赠用户活动的视图控制器(parentViewController),然后从其视图中提取userActivity。我添加了更多代码来展示这一点。 - rmooney

-1

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

在为暴露参数的意图设置默认值之后,这些参数被传递给INShortcut(然后分配给INUIAddVoiceShortcutButton),所有按钮都正确地更新了它们的状态!


-1

所以我们不能使用默认的Siri按钮,必须使用自定义UIButton。 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)
    }

}
}

这段代码的灵感/复制来自于这个网页: https://www.nodesagency.com/test-drive-a-siri-shortcuts-intro/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接