UITextField format in xx-xx-xxx

45,864

Solution 1

Try below it will work

Objective-C

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    int groupingSize = 2;
    if([string length] == 0) {
        groupingSize = 4;
    }
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init] ;
    NSString *separator = @"-";
    [formatter setGroupingSeparator:separator];
    [formatter setGroupingSize:groupingSize];
    [formatter setUsesGroupingSeparator:YES];
    [formatter setSecondaryGroupingSize:2];
    if (![string  isEqual: @""] && (textField.text != nil && textField.text.length > 0)) {
        NSString *num = textField.text;
        num = [num stringByReplacingOccurrencesOfString:separator withString:@""];
        NSString *str = [formatter stringFromNumber:[NSNumber numberWithDouble:[num doubleValue]]];
        textField.text = str;
    }
    return YES;
}

Swift-3

extension ViewController: UITextFieldDelegate {

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        var groupSize = 2
        let separator = "-"
        if string.characters.count == 0 {
            groupSize = 4
        }
        let formatter = NumberFormatter()
        formatter.groupingSeparator = separator
        formatter.groupingSize = groupSize
        formatter.usesGroupingSeparator = true
        formatter.secondaryGroupingSize = 2
        if var number = textField.text, string != "" {
            number = number.replacingOccurrences(of: separator, with: "")
            if let doubleVal = Double(number) {
                let requiredString = formatter.string(from: NSNumber.init(value: doubleVal))
                textField.text = requiredString
            }

        }
        return true
    }
}

Solution 2

Had a need to do this nicely for a phone numbers with a variable format, here's what I wrote. Feel free to reuse - First I've got a method to filter a formatted string, with # being a number, and any other character being some kind of "filler" that should be conveniently inserted after the user gets to the place where it's needed.

NSMutableString *filteredPhoneStringFromStringWithFilter(NSString *string, NSString *filter)
{
    NSUInteger onOriginal = 0, onFilter = 0, onOutput = 0;
    char outputString[([filter length])];
    BOOL done = NO;

    while(onFilter < [filter length] && !done)
    {
        char filterChar = [filter characterAtIndex:onFilter];
        char originalChar = onOriginal >= string.length ? '\0' : [string characterAtIndex:onOriginal];
        switch (filterChar) {
            case '#':
                if(originalChar=='\0')
                {
                    // We have no more input numbers for the filter.  We're done.
                    done = YES;
                    break;
                }
                if(isdigit(originalChar))
                {
                    outputString[onOutput] = originalChar;
                    onOriginal++;
                    onFilter++;
                    onOutput++;
                }
                else
                {
                    onOriginal++;
                }
                break;
            default:
                // Any other character will automatically be inserted for the user as they type (spaces, - etc..) or deleted as they delete if there are more numbers to come.
                outputString[onOutput] = filterChar;
                onOutput++;
                onFilter++;
                if(originalChar == filterChar)
                    onOriginal++;
                break;
        }
    }
    outputString[onOutput] = '\0'; // Cap the output string
    return [NSString stringWithUTF8String:outputString];
}

Now so that they can delete through filler, I modified my should change characters in range.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    NSString *filter = @"(###) ### - ####";

    if(!filter) return YES; // No filter provided, allow anything

    NSString *changedString = [textField.text stringByReplacingCharactersInRange:range withString:string];

    if(range.length == 1 && // Only do for single deletes
       string.length < range.length &&
       [[textField.text substringWithRange:range] rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"0123456789"]].location == NSNotFound)
    {
        // Something was deleted.  Delete past the previous number
        NSInteger location = changedString.length-1;
        if(location > 0)
        {
            for(; location > 0; location--)
            {
                if(isdigit([changedString characterAtIndex:location]))
                {
                    break;
                }
            }
            changedString = [changedString substringToIndex:location];
        }
    }

    textField.text = filteredPhoneStringFromStringWithFilter(changedString, filter);

    return NO;
}

This provides a really clean way to force users to enter numbers in a particular format.

Solution 3

Implement your logic inside textField:shouldChangeCharactersInRange:replacementString: which is a delegate method.

Solution 4

My solution works like that:

enter image description here

Implement in your textfield delegates:

func textFieldDidBeginEditing(_ textField: UITextField) {
        // When you start editing check if there is nothing, in that case add the entire mask
        if let text = textField.text, text == "" || text == "DD/MM/YYYY" {
            textField.text = "DD/MM/YYYY"
            textField.textColor = .lightGray
            textField.setCursor(position: text.count)
        }
    }

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    guard var number = textField.text else {
        return true
    }
    // If user try to delete, remove the char manually
    if string == "" {
        number.remove(at: number.index(number.startIndex, offsetBy: range.location))
    }
    // Remove all mask characters
    number = number.replacingOccurrences(of: "/", with: "")
    number = number.replacingOccurrences(of: "D", with: "")
    number = number.replacingOccurrences(of: "M", with: "")
    number = number.replacingOccurrences(of: "Y", with: "")

    // Set the position of the cursor
    var cursorPosition = number.count+1
    if string == "" {
        //if it's delete, just take the position given by the delegate
        cursorPosition = range.location
    } else {
        // If not, take into account the slash
        if cursorPosition > 2 && cursorPosition < 5 {
            cursorPosition += 1
        } else if cursorPosition > 4 {
            cursorPosition += 2
        }
    }
    // Stop editing if we have rich the max numbers
    if number.count == 8 { return false }
    // Readd all mask char
    number += string
    while number.count < 8 {
        if number.count < 2 {
            number += "D"
        } else if number.count < 4 {
            number += "M"
        } else {
            number += "Y"
        }
    }
    number.insert("/", at: number.index(number.startIndex, offsetBy: 4))
    number.insert("/", at: number.index(number.startIndex, offsetBy: 2))

    // Some styling
    let enteredTextAttribute = [NSForegroundColorAttributeName: UIColor.black, NSFontAttributeName: UIFont.systemFont(ofSize: 15)]
    let maskTextAttribute = [NSForegroundColorAttributeName: UIColor.lightGray, NSFontAttributeName: UIFont.systemFont(ofSize: 15)]

    let partOne = NSMutableAttributedString(string: String(number.prefix(cursorPosition)), attributes: enteredTextAttribute)
    let partTwo = NSMutableAttributedString(string: String(number.suffix(number.count-cursorPosition)), attributes: maskTextAttribute)

    let combination = NSMutableAttributedString()

    combination.append(partOne)
    combination.append(partTwo)

    textField.attributedText = combination
    textField.setCursor(position: cursorPosition)


    return false
}

func textFieldDidEndEditing(_ textField: UITextField) {
    if let text = textField.text, text != "" && text != "DD/MM/YYYY" {
        // Do something with your value
    } else {
        textField.text = ""
    }
}

And that little helper as an extension:

extension UITextField {
    func setCursor(position: Int) {
        let position = self.position(from: beginningOfDocument, offset: position)!
        selectedTextRange = textRange(from: position, to: position)
    }
}

PS: there is still a bug in that implementation when you try to move the cursor while editing

Solution 5

Here is my take on it after looking at everyone's answers.

Swift 4

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let separator = "-"
    let filler = "X"
    if var number = textField.text, string != "" {
        number = number.replacingOccurrences(of: separator, with: "")
        number = number.replacingOccurrences(of: filler, with: "")
        if number.count == 10 { return false }
        number += string
        while number.count < 10 { number += "X" }
        number.insert("-", at: number.index(number.startIndex,
                                            offsetBy: 6))
        number.insert("-", at: number.index(number.startIndex,
                                            offsetBy: 3))
        textField.text = number
    }
    return false
}
Share:
45,864
PJR
Author by

PJR

Check my Sample codes on Github: https://github.com/paritsohraval100 Cocoa Controls https://www.cocoacontrols.com/controls/pjr-scrollview-slider https://www.cocoacontrols.com/controls/pjr-nsstring-category https://www.cocoacontrols.com/controls/pjrsignaturedemo https://www.cocoacontrols.com/controls/pulseanimation Please check My Blog: http://iosdevblogs.blogspot.in/

Updated on July 09, 2022

Comments

  • PJR
    PJR almost 2 years

    I am using UITextField and i want that should take character in the format of xx-xx-xxx only numbers.

    any help ?

  • Narayana
    Narayana over 12 years
    remove auto release at NSNumberFormatter init
  • Peter
    Peter almost 11 years
    I don't know why this hasn't gotten more up-votes. It did exactly what I needed it to do with an absolute minimum of fuss: I copied, pasted, changed the very clear format mask (different format needed) and it worked flawlessly. Perfect.
  • Chris Klingler
    Chris Klingler over 10 years
    To make this idea work for dates and times (including am/pm). 1. NSString *filter = @"##/##/#### ##:## ##"; 2. Char set - [NSCharacterSet characterSetWithCharactersInString:@"0123456789APMapm"], 3. Add else to filteredPhoneStringFromStringWithFilter type method - else if((originalChar == 'a') || (originalChar == 'p') || (originalChar == 'm') || (originalChar == 'A') || (originalChar == 'P') || (originalChar == 'M')){ outputString[onOutput] = originalChar; onOriginal++; onFilter++; onOutput++; }
  • Andre Cytryn
    Andre Cytryn about 9 years
    how to fit a filter like this: (##) ####-####. When I try this, the first parenthesis is never deleted.
  • BadPirate
    BadPirate about 9 years
    @AndréCytryn - Thats because this was designed to enforce the format... If the first few characters aren't variable (not #) then they will always be present (can't be deleted) -- Though you can just modify the methods above to suit your need :)
  • Frederic Adda
    Frederic Adda over 8 years
    You should explain a bit more what your code does, it is really hard to understand ... don't forget that the one reading this hasn't created your code : what may seem obvious to you is not to anyone reading it. Thanks !
  • Inuyasha
    Inuyasha about 8 years
    @Narayana it' working but if my string is leading by 0, it will remove 0 automatically. What should I do to prevent it from remove 0 automatically?
  • Abhishek Thapliyal
    Abhishek Thapliyal almost 7 years
    Can you add swift version also as 2 -3 changes are required in swift to avoid crash
  • Narayana
    Narayana almost 7 years
    @AbhishekThapliyal please check updated answer i have added swift3 code, Still if you face any issue let me know
  • Abhishek Thapliyal
    Abhishek Thapliyal almost 7 years
    @Narayana : If i need to use '\' in place of '-' how i can do that also set character limit?
  • Narayana
    Narayana almost 7 years
    @AbhishekThapliyal please check above updated answer let separator = "-" in place of - use \\ it will work as you required
  • Abhishek Thapliyal
    Abhishek Thapliyal almost 7 years
    @Narayana: Thanks alot!!! one last help how can i make format: xx-xx-xxxx i.e dd-mm-yyyy
  • Tamil
    Tamil about 6 years
    this works fine but i am not able to delete a number or place cursor inbetween and change a number
  • karthikeyan
    karthikeyan about 5 years
    Did you complete this without bug? If yes share demo project Thanks in advance
  • Fast
    Fast over 4 years
    doesn't work correctly when i try to delete the text from textfield
  • Dilip Tiwari
    Dilip Tiwari about 4 years
    How to enter 9 digits in textfield like XXX-XXXXXX in swift
  • Alirza Eram
    Alirza Eram over 2 years
    you have to use string.count instead of string.characters.count for Swift 4 or later!