Funkenstrahlen Podcasting, Netzpolitik, App-Entwicklung

How to use INUIAddVoiceShortcutButton correctly with INUIAddVoiceShortcutButtonDelegate

Siri shortcuts is a big headline feature Apple introduced with iOS 12. It allows users to add custom phrases to Siri for specific actions offered by apps. These shortcut actions can be offered by the app to the users in multiple ways. One important way is to show a button inside the app.

You can design a custom button but Apple encourages you to use the official INUIAddVoiceShortcutButton. You can define its appearance (black or white) and it offers some pretty nice functionality if it is used correctly.

The official documentation for INUIAddVoiceShortcutButton offers some example code:

// Add an "Add to Siri" button to a view.
func addSiriButton(to view: UIView) {
    let button = INUIAddVoiceShortcutButton(style: .blackOutline)
    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) {
    if let shortcut = INShortcut(intent: orderSoupOfTheDayIntent) {
        let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcut)
        viewController.modalPresentationStyle = .formSheet
        viewController.delegate = self // Object conforming to `INUIAddVoiceShortcutViewControllerDelegate`.
        present(viewController, animated: true, completion: nil)
    }
}

However this example code does not offer a good user experience!

  • The button does not automatically change its UI when the shortcut has already been added by the user
  • Tapping the button does not allow to edit the shortcut when it has been created already

This can be done a lot better. Unfortunately Apple does not explain this in their documentation.

The key to a better user experience is INUIAddVoiceShortcutButtonDelegate. It allows you to present either INUIAddVoiceShortcutViewController or INUIEditVoiceShortcutViewController depending on whether the user has already added a custom phrase to this shortcut.

The delegate can be implemented like this:

extension MyViewController: 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)
    }
}

Do not forget to assign the INUIAddVoiceShortcutButtonDelegate after creating the button:

let button = INUIAddVoiceShortcutButton(style: .black)
button.translatesAutoresizingMaskIntoConstraints = false
button.shortcut = INShortcut(intent: WarningLevelIntent())!
button.delegate = self
// then add the button as a subview and create constraints to place it correctly

You also need to set button.shortcut to the shortcut you want to be managed by this button. This will automatically update the button to display a different UI when the user has already added a custom phrase to the shortcut. When the button is tapped the correct INUIAddVoiceShortcutButtonDelegate function is called to either allow assigning the custom phrase or to edit the existing one.

The only thing that’s still missing is the implementation of the INUIAddVoiceShortcutViewControllerDelegate and INUIEditVoiceShortcutViewControllerDelegate. This is a very minimal implementation and you can extend this to your needs:

extension MyViewController: INUIAddVoiceShortcutViewControllerDelegate {
    func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {
        controller.dismiss(animated: true, completion: nil)
    }

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

extension MyViewController: INUIEditVoiceShortcutViewControllerDelegate {
    func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) {
        controller.dismiss(animated: true, completion: nil)
    }

    func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) {
        controller.dismiss(animated: true, completion: nil)
    }

    func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) {
        controller.dismiss(animated: true, completion: nil)
    }
}

I hope this helps someone trying to add a INUIAddVoiceShortcutButton correctly to his app. When I tried to implement this I could not find any good documentation on how to do this. This is why I decided to write this little blog post for you.