Override UITextView's CMD + Z key command

Viewed 169

Is it possible to override UITextView's handling of cmd + z and cmd + shift + z ?

I've tried to

  • override the keyCommand property, but the selectors are never called..
  • override the undoManager, this doesn't help either
class CustomTextView: UITextView {

    override var keyCommands: [UIKeyCommand]? {
       [
            // cmd + z (doesn't work)
            UIKeyCommand(input: "z", modifierFlags: [.command], action: #selector(undo)),
                
            // cmd + shift + z  (doesn't work)
            UIKeyCommand(input: "z", modifierFlags: [.command, .shift], action: #selector(redo)),
                
            // z (works)
            UIKeyCommand(input: "z", modifierFlags: [], action: #selector(z)),
        ]
    }
     
    // this doesn't help   
    override var undoManager: UndoManager? { return nil }
        






    // undo
    @objc private func undo() {
        print("undo")
    }

    // redo
    @objc private func redo() {
        print("redo")
    }

    // z
    @objc private func z() {
        print("z")
    }   
}
1 Answers

You can do this with a UITextView+UIResponder.swift extension:

import UIKit

extension UITextView {
    static var myUndoManager = UndoManager()

    fileprivate func handleUndo () { ... }
    fileprivate func handleRedo () { ... }

    override var undoManager : UndoManager? {
        return Self.myUndoManager
    }

    override func pressesBegan (
        _ presses: Set<UIPress>
      , with event: UIPressesEvent?
    )
    {
        for press in presses {
            guard
                let key = press.key
              , key.charactersIgnoringModifiers == "z"
            else { continue }

            switch key.modifierFlags {
                case .command:
                    handleUndo()
                case [ .command, .shift]:
                    handleRedo()
                default:
                    break
            }
        }
        super.pressesBegan (
            presses
          , with: event
        )
    }
}

Relevant Apple References:

UIResponder::func pressesBegan(_:) https://developer.apple.com/documentation/uikit/uiresponder

Keyboards & Input::class UIKey https://developer.apple.com/documentation/uikit/uikey

See Also: https://stackoverflow.com/questions/tagged/uiresponder

Why won't overriding keyCommands work?

UITextView is opaque, so I can't see for myself , but I think it overrides pressesBegan(_:with:) and does not call super for any key-press it handles. Without that call to super, UIPressesEvent events never propagate up the responder chain; keyCommands will never see them.

Related