0

I’m a junior MacOS developer. In Swift 4 Cocoa I would like to have Text Field with "special" handling of the Shift key.

By default, if a letter is entered it should be uppercased BUT if the Shift key is pressed the letter should be in lower case.

Exemple:

  • “a” is entered -> transformed to “A”
  • Shift+”a” is entered -> transformed to “a”
  • any other entry (number etc.)should be handled normally-

I was able to force to uppercase with

override func controlTextDidChange(_ obj: Notification) {
    print("Text changed")

    var infoDictionary:Dictionary = obj.userInfo! as Dictionary
    let text:NSText = infoDictionary["NSFieldEditor"] as! NSText
    text.string = text.string.uppercased()
}

However I don't know how to "force" to lower case when the shift key is pressed.

I am very grateful for your reading and...help.

Eric
  • 1,210
  • 8
  • 25
Dids
  • 125
  • 1
  • 9

2 Answers2

0

Address the task at the input stage, not after text has been entered into the field. That is, convert the case of incoming key events before they are inserted into the text field.

Apple uses a field editor setup which effectively replaces any NSTextField with a NSTextView while it is being edited. The process is explained in Working with the Field Editor, and in overview is:

  • You define an subclass of NSTextView which overrides keyDown: modifying the case of characters as required before calling the overridden method ([super ...]). Your override will either pass the received NSEvent unmodified or construct a new one with your case modifications.

  • The window that contains your text field is responsible for setting the field editor. In your window delegate you implement the delegate method windowWillReturnFieldEditor:toObject:. If the second argument to this method is a text field for which you wish to have special handling you return an instance of your NSTextView subclass with its fieldEditor property set to YES. Otherwise you return nil.

  • An easy way to recognise text fields which require special handling is to declare an empty subclass of NSTextField and use that for the special text fields. Your window delegate method can now just do a type test.

If you have problems working out the solution ask a new question showing the code you have developed, explaining your problems, etc. and someone will undoubtedly help you with the next step.

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Subclassing `NSTextField` doesn't work, the field editor handles the key events. Overriding `keyDown(with:)` doesn't work for ü and é. – Willeke Oct 19 '17 at 13:59
  • @Willeke - Egg-on-face, thanks for spotting that. Fixed the answer to override the correct `keyDown:`. – CRD Oct 19 '17 at 20:07
  • @Willeke - That's why I said "effectively" ;-) Apple themselves write "This mechanism can be confusing" and I think describing what is really going on would just obfuscate matters. – CRD Oct 19 '17 at 22:50
  • Saying "replacing" and "NSTextField/NSTextView" is confusing. Subclasses of `NSControl` use a field editor, `NSTextView` doesn't. A control isn't replaced, it's still there and functioning. – Willeke Oct 20 '17 at 11:46
0

Keystrokes in a text field are handled by The Field Editor. The Field Editor is a NSTextView and NSTextView implements the NSTextInputClient protocol. Create a subclass of NSTextView and override insertText(_:replacementRange:) to change the case of the input string.

class MyTextView: NSTextView {

    override func insertText(_ string: Any, replacementRange: NSRange) {
        var newString = string

        if let oldString = string as? String {
            if (NSApp.currentEvent?.modifierFlags.contains(.shift))! {
                newString = oldString.lowercased()
            }
            else {
                newString = oldString.uppercased()
            }
        }
        // handle string is a NSAttributedString

        super.insertText(newString, replacementRange:replacementRange)
    }

}

Subclass NSTextFieldCell and override fieldEditor(for:) to return an instance of the subclass of NSTextView.

class MyTextFieldCell: NSTextFieldCell {

    var myFieldEditor:MyTextView? = nil

    override func fieldEditor(for controlView: NSView) -> NSTextView? {
        if (myFieldEditor == nil) {
            myFieldEditor = MyTextView()
            myFieldEditor?.isFieldEditor = true
        }
        return myFieldEditor  
    }

}

Set the class of the cell of the text field to MyTextFieldCell.

Willeke
  • 14,578
  • 4
  • 19
  • 47