Luka Kerr
Luka Kerr

Reputation: 4239

keyDown doesn't update NSTextView immediately

I have a keyDown function in my application that is used to capture input from a NSTextView named textInput. Some conversions are done with the input which is appended as a NSAttributedString back into the NSTextView.

This works fine currently, but the problem I have is that the value entered into the textbox on keyDown doesn't get added to the textInput.textStorage?.string, until another key is pressed.

For example if I enter the text abcde and nothing more into textInput, and then inside func keyDown() I try to access textInput.textStorage?.string, it will return abcd.

Here is the function without unnecessary parts:

override func keyDown(with event: NSEvent) {
    let bottomBox = textInput.textStorage?.string // This returns one character short of what is actually in the text box

    if let bottomBox = bottomBox {
        var attribute = NSMutableAttributedString(string: bottomBox)

        // Do some stuff here with bottomBox and attribute

        // Clear and set attributed string
        textInput.textStorage?.mutableString.setString("")
        textInput.textStorage?.append(attribute)
    }
}

If I were to use keyUp, this isn't a problem, although the problem with keyUp is that if the user holds down the key, the attributes on the NSAttributedString don't get set until the user releases the key.

I though maybe there was a way to programatically release the keyDown event during the keyDown function, or generate a keyUp event, but can't seem to find anything.

Is there a way to fix this?

Upvotes: 0

Views: 266

Answers (1)

Charles Srstka
Charles Srstka

Reputation: 17040

What I like to do is to use Cocoa Bindings with a property observer. Set up your properties like so:

class MyViewController: NSViewController {
    @objc dynamic var textInput: String {
        didSet { /* put your handler here */ }
    }

    // needed because NSTextView only has an "Attributed String" binding
    @objc private static let keyPathsForValuesAffectingAttributedTextInput: Set<String> = [
        #keyPath(textInput)
    ]
    @objc private var attributedTextInput: NSAttributedString {
        get { return NSAttributedString(string: self.textInput) }
        set { self.textInput = newValue.string }
    }
}

Now bind your text view to attributedTextInput with the "Continuously Updates Value" check box checked:

enter image description here

Et voilà, your property will be immediately updated every time you type a character, and your property's didSet will immediately be called.

Upvotes: 1

Related Questions