Keyboard shortcuts with UIKeyCommand in iPadOS 15 beta

1.5k views Asked by At

For some reason I can't get hardware keyboard shortcuts to work in iPadOS 15 (beta 5). They work for most keys, but not for arrow keys and tab key.

The same code seems to work well when compiled in Xcode 13 (beta 4) and run on iPadOS 14.5 simulator, but then refuses to work when built with same Xcode but on iPadOS 15 sim. I've tried it on actual devices with iPadOS 15 betas up to 5 with same results.

Here is a minimal example:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        addKeyCommand(UIKeyCommand(title: "UP", action: #selector(handle(key:)), input: UIKeyCommand.inputUpArrow, modifierFlags: []))
        addKeyCommand(UIKeyCommand(title: "DOWN", action: #selector(handle(key:)), input: UIKeyCommand.inputDownArrow, modifierFlags: []))
        addKeyCommand(UIKeyCommand(title: "TAB", action: #selector(handle(key:)), input: "\t", modifierFlags: []))
    }

    @objc func handle(key: UIKeyCommand?) {
        NSLog("Intercepted key: \(key?.title ?? "Unknown")")
    }
}

I haven't found any related reports or open radars, so I'm suspecting I could be missing something here. If this should be reported, where do I report a bug like that?

Thank you.

2

There are 2 answers

2
Alex Staravoitau On BEST ANSWER

Apparently, there is a new UIKeyCommand property wantsPriorityOverSystemBehavior, which needs to be set to true for some keys — like the ones I mentioned in my question: https://developer.apple.com/documentation/uikit/uikeycommand/3780513-wantspriorityoversystembehavior

1
Nat On

The solution is indeed to use wantsPriorityOverSystemBehavior. However, since you're using a subclass of UIResponder instead of adding the key commands for known keys, you may consider using the built-in method. It's more effective than adding each one separately and it's just more clean in terms of a pattern.

class ViewController: UIViewController {

    /// - SeeAlso: UIViewController.viewDidLoad()
    override func viewDidLoad() {
        super.viewDidLoad()
        // do other things, nothing connected with UIKeyCommand
    }

    /// - SeeAlso: UIResponder.keyCommands
    override var keyCommands: [UIKeyCommand]? {
        let commands = [
            UIKeyCommand(input: UIKeyCommand.inputUpArrow, modifierFlags: [], action: #selector(actionUp)),
            UIKeyCommand(input: UIKeyCommand.inputDownArrow, modifierFlags: [], action: #selector(actionDown)),
            UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: [], action: #selector(actionLeft)),
            UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: [], action: #selector(actionRight))
        ]
        // if your target is iOS 15+ only, you can remove `if` and always apply this rule
        if #available(iOS 15, *) { 
            commands.forEach { $0.wantsPriorityOverSystemBehavior = true }
        }
        return commands
    }
}

private extension ViewController {
   
    @objc func actionUp() { print("up") }
    @objc func actionDown() { print("down") }
    @objc func actionLeft() { print("left") }
    @objc func actionRight() { print("right") }
}

I'm sorry it's an answer, but comments don't allow for a nice code syntax :-) Hope you don't mind my addition, but maybe someone doesn't know about keyCommands and find it useful in this context. At least I presented an example of how to use this new part of API which the creator @AlexStaravoitau already wrote about.


Apple claims, that:

Prior to iOS 15, the system delivered keyboard events to your key command objects first, and then to the text input or focus systems. If your app links against iOS 14 SDK or earlier, your app retains that behavior, even when running on iOS 15 or later.

and it's a pretty important little detail worth to keep in mind. I misread it and thought if you support iOS 14 or lower it'll work the old way. But it was my wrong understanding, and it's about linking at all (e.g. if you build with Xcode 12 or older and don't add extra iOS in the library). So, even though my app still supports iOS 12+, the key commands no longer works on new builds from Xcode 13 on iOS 15. It started to work only once I adapted the mentioned flag.