swiftmacosnssearchfieldnumberformatter

NumberFormatter for NSSearchField input


I have an NSSearchField where the user can type in a number with decimals to search through data. I am having problems with using a NumberFormatter to display the correct format.

I added this in IB:

enter image description here

What happens is, as soon as the user types the decimal point (or whatever the local character for that is), the field becomes empty. Same happens when I type a letter (this is expected), so it seems that the decimal point gets rejected as if it is a letter. Of course, not what I want.

Tried many different combinations of those checkboxes halfway the picture (including "Generate Decimal Numbers"), but cannot get it to work.

If you look at the sample at the bottom of the picture, that looks ok.

And there is nothing in the code that manipulates the search field.


Solution

  • I finally solved it by completely dropping the formatter and just filter the string in controlTextDidChange. Note that this works for both NSTextField and NSSearchField.

    class ViewController: NSViewController {
        @IBOutlet var searchField: NSSearchField! // delegate is set in SB
    
        lazy var decimalCharacterSet: CharacterSet = {
            var charSet = CharacterSet.init(charactersIn: "0123456789")
    
            charSet.insert(charactersIn: Locale.current.decimalSeparator!)
    
            return charSet
        }()
    }
    
    extension ViewController: NSControlTextEditingDelegate {
        func controlTextDidChange(_ notification: Notification) {
            if let textField = notification.object as? NSTextField {
                // first filter out all the non-numbers
                let chars = textField.stringValue.components(separatedBy: decimalCharacterSet.inverted)
                var result = chars.joined()
    
                // now check if there are more than one decimal separators
                let count = result.filter { $0 == Character(Locale.current.decimalSeparator!) }.count
    
                // if so, remove the last character in the string, this is what has just been typed.
                if count > 1 {
                    result = String(result.dropLast())
                }
    
                // finally, assign the filtered string back to the textField
                textField.stringValue = result
             }
        }
    }