Formatting a UITextField for credit card input like (xxxx xxxx xxxx xxxx)

前端 未结 28 2078
长情又很酷
长情又很酷 2020-11-28 01:19

I want to format a UITextField for entering a credit card number into such that it only allows digits to be entered and automatically inserts spaces so that the

相关标签:
28条回答
  • 2020-11-28 01:53

    Swift 3 solution using Fawkes answer as basic. Added Amex Card format support. Added reformation when card type changed.

    First make new class with this code:

    extension String {
    
        func containsOnlyDigits() -> Bool
        {
    
            let notDigits = NSCharacterSet.decimalDigits.inverted
    
            if rangeOfCharacter(from: notDigits, options: String.CompareOptions.literal, range: nil) == nil
            {
                return true
            }
    
            return false
        }
    }
    import UIKit
    
    var creditCardFormatter : CreditCardFormatter
    {
        return CreditCardFormatter.sharedInstance
    }
    
    class CreditCardFormatter : NSObject
    {
        static let sharedInstance : CreditCardFormatter = CreditCardFormatter()
    
        func formatToCreditCardNumber(isAmex: Bool, textField : UITextField, withPreviousTextContent previousTextContent : String?, andPreviousCursorPosition previousCursorSelection : UITextRange?) {
            var selectedRangeStart = textField.endOfDocument
            if textField.selectedTextRange?.start != nil {
                selectedRangeStart = (textField.selectedTextRange?.start)!
            }
            if  let textFieldText = textField.text
            {
                var targetCursorPosition : UInt = UInt(textField.offset(from:textField.beginningOfDocument, to: selectedRangeStart))
                let cardNumberWithoutSpaces : String = removeNonDigitsFromString(string: textFieldText, andPreserveCursorPosition: &targetCursorPosition)
                if cardNumberWithoutSpaces.characters.count > 19
                {
                    textField.text = previousTextContent
                    textField.selectedTextRange = previousCursorSelection
                    return
                }
                var cardNumberWithSpaces = ""
                if isAmex {
                    cardNumberWithSpaces = insertSpacesInAmexFormat(string: cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
                }
                else
                {
                    cardNumberWithSpaces = insertSpacesIntoEvery4DigitsIntoString(string: cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
                }
                textField.text = cardNumberWithSpaces
                if let finalCursorPosition = textField.position(from:textField.beginningOfDocument, offset: Int(targetCursorPosition))
                {
                    textField.selectedTextRange = textField.textRange(from: finalCursorPosition, to: finalCursorPosition)
                }
            }
        }
    
        func removeNonDigitsFromString(string : String, andPreserveCursorPosition cursorPosition : inout UInt) -> String {
            var digitsOnlyString : String = ""
            for index in stride(from: 0, to: string.characters.count, by: 1)
            {
                let charToAdd : Character = Array(string.characters)[index]
                if isDigit(character: charToAdd)
                {
                    digitsOnlyString.append(charToAdd)
                }
                else
                {
                    if index < Int(cursorPosition)
                    {
                        cursorPosition -= 1
                    }
                }
            }
            return digitsOnlyString
        }
    
        private func isDigit(character : Character) -> Bool
        {
            return "\(character)".containsOnlyDigits()
        }
    
        func insertSpacesInAmexFormat(string : String, andPreserveCursorPosition cursorPosition : inout UInt) -> String {
            var stringWithAddedSpaces : String = ""
            for index in stride(from: 0, to: string.characters.count, by: 1)
            {
                if index == 4
                {
                    stringWithAddedSpaces += " "
                    if index < Int(cursorPosition)
                    {
                        cursorPosition += 1
                    }
                }
                if index == 10 {
                    stringWithAddedSpaces += " "
                    if index < Int(cursorPosition)
                    {
                        cursorPosition += 1
                    }
                }
                if index < 15 {
                   let characterToAdd : Character = Array(string.characters)[index]
                    stringWithAddedSpaces.append(characterToAdd)
                }
            }
            return stringWithAddedSpaces
        }
    
    
        func insertSpacesIntoEvery4DigitsIntoString(string : String, andPreserveCursorPosition cursorPosition : inout UInt) -> String {
            var stringWithAddedSpaces : String = ""
            for index in stride(from: 0, to: string.characters.count, by: 1)
            {
                if index != 0 && index % 4 == 0 && index < 16
                {
                    stringWithAddedSpaces += " "
    
                    if index < Int(cursorPosition)
                    {
                        cursorPosition += 1
                    }
                }
                if index < 16 {
                    let characterToAdd : Character = Array(string.characters)[index]
                    stringWithAddedSpaces.append(characterToAdd)
                }
            }
            return stringWithAddedSpaces
        }
    
    }
    

    In your ViewControllerClass add this function

    func reformatAsCardNumber(textField:UITextField){
      let formatter = CreditCardFormatter()
      var isAmex = false
      if selectedCardType == "AMEX" {
        isAmex = true
        }
      formatter.formatToCreditCardNumber(isAmex: isAmex, textField: textField, withPreviousTextContent: textField.text, andPreviousCursorPosition: textField.selectedTextRange)
    }
    

    Then add target to your textField

    youtTextField.addTarget(self, action: #selector(self.reformatAsCardNumber(textField:)), for: UIControlEvents.editingChanged)
    

    Register new variable and sent card type to it

    var selectedCardType: String? {
      didSet{
        reformatAsCardNumber(textField: yourTextField)
      }
    }
    

    Thanks Fawkes for his code!

    0 讨论(0)
  • 2020-11-28 01:54

    You can probably optimize my code or there might be an easier way but this code should work:

    -(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    
        __block NSString *text = [textField text];
    
        NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789\b"];
        string = [string stringByReplacingOccurrencesOfString:@" " withString:@""];
        if ([string rangeOfCharacterFromSet:[characterSet invertedSet]].location != NSNotFound) {
            return NO;
        }
    
        text = [text stringByReplacingCharactersInRange:range withString:string];
        text = [text stringByReplacingOccurrencesOfString:@" " withString:@""];
    
        NSString *newString = @"";
        while (text.length > 0) {
            NSString *subString = [text substringToIndex:MIN(text.length, 4)];
            newString = [newString stringByAppendingString:subString];
            if (subString.length == 4) {
                newString = [newString stringByAppendingString:@" "];
            }
            text = [text substringFromIndex:MIN(text.length, 4)];
        }
    
        newString = [newString stringByTrimmingCharactersInSet:[characterSet invertedSet]];
    
        if (newString.length >= 20) {
            return NO;
        }
    
        [textField setText:newString];
    
        return NO;
    }
    
    0 讨论(0)
  • 2020-11-28 01:54

    Check Out This Solution. I found in Autorize.net SDK Example.

    Make Your UITextField Keyboard Type to Numeric.

    It Will Mask Credit Card Numbers With 'X' And By Adding Spaces It Will Make 'XXXX XXXX XXXX 1234' format.

    In Header .h file

        #define kSpace @" "
        #define kCreditCardLength 16
        #define kCreditCardLengthPlusSpaces (kCreditCardLength + 3)
        #define kCreditCardObscureLength (kCreditCardLength - 4)
    
        @property (nonatomic, strong) NSString *creditCardBuf;
        IBOutlet UITextField *txtCardNumber;
    

    In .m file

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        if (textField == txtCardNumber) {
            if ([string length] > 0) { //NOT A BACK SPACE Add it
    
                if ([self isMaxLength:textField])
                    return NO;
    
                self.creditCardBuf  = [NSString stringWithFormat:@"%@%@", self.creditCardBuf, string];
            } else {
    
                //Back Space do manual backspace
                if ([self.creditCardBuf length] > 1) {
                    self.creditCardBuf = [self.creditCardBuf substringWithRange:NSMakeRange(0, [self.creditCardBuf length] - 1)];
                } else {
                    self.creditCardBuf = @"";
                }
            }
            [self formatValue:textField];
        }
    
        return NO;
    }
    
    - (BOOL) isMaxLength:(UITextField *)textField {
    
        if (textField == txtCardNumber && [textField.text length] >= kCreditCardLengthPlusSpaces) {
            return YES;
        }
        return NO;
    }
    
    - (void) formatValue:(UITextField *)textField {
        NSMutableString *value = [NSMutableString string];
    
        if (textField == txtCardNumber) {
            NSInteger length = [self.creditCardBuf length];
    
            for (int i = 0; i < length; i++) {
    
                // Reveal only the last character.
                if (length <= kCreditCardObscureLength) {
    
                    if (i == (length - 1)) {
                        [value appendString:[self.creditCardBuf substringWithRange:NSMakeRange(i,1)]];
                    } else {
                        [value appendString:@“X”];
                    }
                }
                // Reveal the last 4 characters
                else {
    
                    if (i < kCreditCardObscureLength) {
                        [value appendString:@“X”];
                    } else {
                        [value appendString:[self.creditCardBuf substringWithRange:NSMakeRange(i,1)]];
                    }
                }
    
                //After 4 characters add a space
                if ((i +1) % 4 == 0 &&
                    ([value length] < kCreditCardLengthPlusSpaces)) {
                    [value appendString:kSpace];
                }
            }
            textField.text = value;
        }
    }
    
    0 讨论(0)
  • 2020-11-28 01:55

    These answers are all just way too much code for me. Here's a solution in Swift 2.2.1

    extension UITextField {
    
        func setText(to newText: String, preservingCursor: Bool) {
            if preservingCursor {
                let cursorPosition = offsetFromPosition(beginningOfDocument, toPosition: selectedTextRange!.start) + newText.characters.count - (text?.characters.count ?? 0)
                text = newText
                if let newPosition = positionFromPosition(beginningOfDocument, offset: cursorPosition) {
                    selectedTextRange = textRangeFromPosition(newPosition, toPosition: newPosition)
                }
            }
            else {
                text = newText
            }
        }
    }
    

    Now just put an IBAction in your view controller:

    @IBAction func textFieldEditingChanged(sender: UITextField) {
        var digits = current.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("") // remove non-digits
        // add spaces as necessary or otherwise format your digits.
        // for example for a phone number or zip code or whatever
        // then just:
        sender.setText(to: digits, preservingCursor: true)
    }
    
    0 讨论(0)
  • 2020-11-28 01:58

    Swift 3.2

    Little correction in the @Lucas answer and working code in swift 3.2. Also removing the space character automatically.

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
        if range.location == 19 {
            return false
        }
    
        if range.length == 1 {
            if (range.location == 5 || range.location == 10 || range.location == 15) {
                let text = textField.text ?? ""
                textField.text = text.substring(to: text.index(before: text.endIndex))
            }
            return true
        }
    
        if (range.location == 4 || range.location == 9 || range.location == 14) {
            textField.text = String(format: "%@ ", textField.text ?? "")
        }
    
        return true
    }
    
    0 讨论(0)
  • 2020-11-28 01:59
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
        {
            if textField == CardNumTxt
            {
                let replacementStringIsLegal = string.rangeOfCharacterFromSet(NSCharacterSet(charactersInString: "0123456789").invertedSet) == nil
    
                if !replacementStringIsLegal
                {
                    return false
                }
    
                let newString = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
                let components = newString.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "0123456789").invertedSet)
    
                let decimalString = components.joinWithSeparator("") as NSString
                let length = decimalString.length
                let hasLeadingOne = length > 0 && decimalString.characterAtIndex(0) == (1 as unichar)
    
                if length == 0 || (length > 16 && !hasLeadingOne) || length > 19
                {
                    let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int
    
                    return (newLength > 16) ? false : true
                }
                var index = 0 as Int
                let formattedString = NSMutableString()
    
                if hasLeadingOne
                {
                    formattedString.appendString("1 ")
                    index += 1
                }
                if length - index > 4
                {
                    let prefix = decimalString.substringWithRange(NSMakeRange(index, 4))
                    formattedString.appendFormat("%@-", prefix)
                    index += 4
                }
    
                if length - index > 4
                {
                    let prefix = decimalString.substringWithRange(NSMakeRange(index, 4))
                    formattedString.appendFormat("%@-", prefix)
                    index += 4
                }
                if length - index > 4
                {
                    let prefix = decimalString.substringWithRange(NSMakeRange(index, 4))
                    formattedString.appendFormat("%@-", prefix)
                    index += 4
                }
    
    
                let remainder = decimalString.substringFromIndex(index)
                formattedString.appendString(remainder)
                textField.text = formattedString as String
                return false
            }
            else
            {
                return true
            }
        }
    

    formattedString.appendFormat("%@-", prefix) chage of "-" any other your choose

    0 讨论(0)
提交回复
热议问题