29

How do I restrict a NSTextField to allow only numbers/integers? I've found questions like this one, but they didn't help!

NSGod
  • 22,699
  • 3
  • 58
  • 66
JomanJi
  • 1,407
  • 1
  • 17
  • 27

11 Answers11

51

Try to make your own NSNumberFormatter subclass and check the input value in -isPartialStringValid:newEditingString:errorDescription: method.

@interface OnlyIntegerValueFormatter : NSNumberFormatter

@end

@implementation OnlyIntegerValueFormatter

- (BOOL)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
{
    if([partialString length] == 0) {
        return YES;
    }

    NSScanner* scanner = [NSScanner scannerWithString:partialString];

    if(!([scanner scanInt:0] && [scanner isAtEnd])) {
        NSBeep();
        return NO;
    }

    return YES;
}

@end

And then set this formatter to your NSTextField:

OnlyIntegerValueFormatter *formatter = [[[OnlyIntegerValueFormatter alloc] init] autorelease];
[textField setFormatter:formatter];
Dmitry
  • 7,300
  • 6
  • 32
  • 55
  • 8
    In Interface Builder, drop an NSObject onto the xib and set it's class to be your custom formatter (like the above). Then set/drag the NSTextFields "formatter" outlet to that NSObject. Then you don't need any code to apply the formatter. – Ali May 22 '13 at 11:05
  • 1
    It is working all fine.... but when ever i tried to enter multiple zeros(0)..like 000000.....its not accepting... any help pls??? – VSN Mar 10 '14 at 10:34
29

Swift 3 Version

import Foundation

    class OnlyIntegerValueFormatter: NumberFormatter {

    override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {

        // Ability to reset your field (otherwise you can't delete the content)
        // You can check if the field is empty later
        if partialString.isEmpty {
            return true
        }

        // Optional: limit input length
        /*
        if partialString.characters.count>3 {
            return false
        }
        */

        // Actual check
        return Int(partialString) != nil
    }
}

Use:

let onlyIntFormatter = OnlyIntegerValueFormatter()
myNsTextField.formatter = onlyIntFormatter
Xavier Daleau
  • 501
  • 5
  • 6
9

Here's a solution with filtering. Give a delegate and an outlet to textfield and set controlTextDidChange method.

- (void)controlTextDidChange:(NSNotification *)aNotification {

    NSTextField *textfield = [notification object];
    NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];

    char *stringResult = malloc([textfield.stringValue length]);
    int cpt=0;
    for (int i = 0; i < [textfield.stringValue length]; i++) {
        unichar c = [textfield.stringValue characterAtIndex:i];
        if ([charSet characterIsMember:c]) {
            stringResult[cpt]=c;
            cpt++;
        }
    }
    stringResult[cpt]='\0';
    textfield.stringValue = [NSString stringWithUTF8String:stringResult];
    free(stringResult);
}
Axel Guilmin
  • 11,454
  • 9
  • 54
  • 64
Luc-Olivier
  • 3,715
  • 2
  • 29
  • 29
8

Try this -

NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[textField setFormatter:formatter];
Slavcho
  • 383
  • 1
  • 7
  • 1
    Thank you for answering, but for some reason it still won't allow only numbers :( But i'll keep trying......btw, where's the code supposed too be? – JomanJi Aug 28 '12 at 15:28
4

Here is a Swift version:

override func isPartialStringValid(partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>) -> Bool {
    if (count(partialString.utf16)) {
        return true
    }

    if (partialString.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) != nil) {
        NSBeep()
        return false
    }

    return true
}
rdougan
  • 7,217
  • 2
  • 34
  • 63
  • 1
    `string.utf16Count` is not available in swift 1.2 anymore. You should change it to `count(partialString.utf16)`. – yinkou May 27 '15 at 07:50
  • It's actually wrong on many levels. First, numbers aren't boolean convertible, and second, you want to check `isEmpty`. If the length of the string is greater than zero, you must fall through – Mazyod Nov 27 '15 at 06:55
4

In SWIFT, I do it this way

  1. Convert the text value to Int with Int()
  2. Check the converted value is not less than 0
  3. If less than 0, display error message other accept the value

    if ((Int(txtField.stringValue)) < 0){
        // Display error message
    }
    
Dirk Stöcker
  • 1,628
  • 1
  • 12
  • 23
David Evony
  • 273
  • 3
  • 15
2

[Works with Swift 3.0.1]

As others suggested, subclass NumberFormatter and override isPartialStringValid method. The easiest way is to drop a NumberFormatter object under your NSTextField in xib/storyboard and update it's Custom Class. Next implementation allows only integers or blank value and plays a beep if string contains illegal characters.

class IntegerFormatter: NumberFormatter {

    override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {

        // Allow blank value
        if partialString.numberOfCharacters() == 0  {
            return true
        }

        // Validate string if it's an int
        if partialString.isInt() {
            return true
        } else {
            NSBeep()
            return false
        }
    }
}

String's numberOfCharacters() and isInt() are methods added in an extension.

extension String {

    func isInt() -> Bool {

        if let intValue = Int(self) {

            if intValue >= 0 {
                return true
            }
        }

        return false
    }

    func numberOfCharacters() -> Int {
        return self.characters.count
    }
}
nsinvocation
  • 7,559
  • 3
  • 41
  • 46
1

Here is the steps to create the same....

Just create the ANYCLASS(called SAMPLE) with sub classing the NSNumberFormatter ...

in .m file write the following code...

 - (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString      **)newString errorDescription:(NSString **)error {
// Make sure we clear newString and error to ensure old values aren't being used
if (newString) { *newString = nil;}
if (error) {*error = nil;}

static NSCharacterSet *nonDecimalCharacters = nil;
if (nonDecimalCharacters == nil) {
    nonDecimalCharacters = [[NSCharacterSet decimalDigitCharacterSet] invertedSet] ;
}

if ([partialString length] == 0) {
    return YES; // The empty string is okay (the user might just be deleting everything and starting over)
} else if ([partialString rangeOfCharacterFromSet:nonDecimalCharacters].location != NSNotFound) {
    return NO; // Non-decimal characters aren't cool!
}

return YES;

}

Now.. in your Actual Class set the formatter to your NSTextField object like below...

NSTextField *mySampleTxtFld;

for this set the Formatter...

SAMPLE* formatter=[[SAMPLE alloc]init];// create SAMPLE FORMATTER OBJECT 

self.mySampleTxtFld.delegate=self;
[self.mySampleTxtFld setFormatter:formatter];

Your done!!!

VSN
  • 2,361
  • 22
  • 30
1

Swift 2.0 custom formatter with 0 instead of empty space :

class OnlyIntegerValueFormatter: NSNumberFormatter {

    override func isPartialStringValid(partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>) -> Bool {

        if partialString.isEmpty {
            newString.memory = "0"
            return false
        }

        if Int(partialString) < 0 {
            NSBeep()
            return false
        } else {
            return true
        }
    }
}
CryingHippo
  • 5,026
  • 1
  • 28
  • 32
1

in swift 5.7:

import Cocoa

class Main: NSViewController {

    @IBOutlet weak var textfield: NSTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        textfield.delegate = self
    }
}
extension Main: NSTextFieldDelegate {
    func controlTextDidChange(_ obj: Notification) {
        let filtered = (obj.object as! NSTextField).stringValue.filter{"0123456789".contains($0)}
        if filtered != (obj.object as! NSTextField).stringValue {
            (obj.object as! NSTextField).stringValue = filtered
        }
    }
}

If you want to allow only specified characters to be entered, you can change the filter rules. like this:

let filtered = (obj.object as! NSTextField).stringValue.filter{"abcdefghijklmnopqrstuvwxyz".contains($0)}

Hope this helps you

Xiaomu Gu
  • 11
  • 2
0
//  NSTextFieldNumberFormatter+Extension.swift

import Foundation

class TextFieldIntegerValueFormatter: NumberFormatter {
var maxLength: Int

init(maxLength: Int) {
    self.maxLength = maxLength
    super.init()
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
    
    // Ability to reset your field (otherwise you can't delete the content)
    // You can check if the field is empty later
    if partialString.isEmpty {
        return true
    }
    
    // Optional: limit input length
    if partialString.count > maxLength {
        return false
    }
    
    // Actual check
    return Int(partialString) != nil
 }
}

//Need to call like:

myNsTextField.formatter = TextFieldIntegerValueFormatter(maxLength: 6)
Mannam Brahmam
  • 2,225
  • 2
  • 24
  • 36