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

前端 未结 28 2167
长情又很酷
长情又很酷 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条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-11-28 01:41

    In order to achieve the goal of format the text entered in the textfield in this way XXXX XXXX XXXX XXXX is important to keep in mind some important things. Beside the fact that the 16 digits card number separated every four digit is the most common used format, there are cards with 15 digits (AmEx formatted XXXX XXXXXX XXXXX) and others with 13 digits or even with 19 digits (https://en.wikipedia.org/wiki/Payment_card_number ). Other important thing you should consider is configure the textField to allow only digits, configure the keyboard type as numberPad is a good start, but is convenient to implement a method which secure the input.

    A starting point is decide when you want to format the number, while the user is entering the number or when the user leave the text field. In the case that you want to format when the user leave the textField is convenient to use the textFieldDidEndEditing(_:) delegate's method take the content of the textField and format it.

    In the case you while the user is entering the number is useful the textField(_:shouldChangeCharactersIn:replacementString:) delegate method which is called whenever the current text changes.

    In both cases there is still a problem, figure out which is the correct format for the entered number, IMHO and based on all the numbers that I have seen, there are only two main formats: the Amex format with 15 digits described above and the format which group card number every four digits which don not care of how much digits there are, being this case like a generic rule, for example a card with 13 digits will be formatted XXXXX XXXX XXXX X and with 19 digits will look like this XXXX XXXX XXXX XXXX XXX, this will work for the most common cases (16 digits) and for the others as well. So you could figure out how to manage the AmEx case with the same algorithm below playing with the magic numbers.

    I used a RegEx to ensure that a 15 digits card is an American express, in the case of other particular formats

    let regex = NSPredicate(format: "SELF MATCHES %@", "3[47][A-Za-z0-9*-]{13,}" )
    let isAmex = regex.evaluate(with: stringToValidate)
    

    I strongly recommend to use the specific RegEx which is useful to identify the Issuer and to figure out how many digits should be accepted.

    Now my swift approach of the solution with textFieldDidEndEditing is

    func textFieldDidEndEditing(_ textField: UITextField) {
    
        _=format(cardNumber: textField.text!)
    
    }
    func format(cardNumber:String)->String{
        var formatedCardNumber = ""
        var i :Int = 0
        //loop for every character
        for character in cardNumber.characters{
            //in case you want to replace some digits in the middle with * for security
            if(i < 6 || i >= cardNumber.characters.count - 4){
                formatedCardNumber = formatedCardNumber + String(character)
            }else{
                formatedCardNumber = formatedCardNumber + "*"
            }
            //insert separators every 4 spaces(magic number)
            if(i == 3 || i == 7 || i == 11 || (i == 15 && cardNumber.characters.count > 16 )){
                formatedCardNumber = formatedCardNumber + "-"
                // could use just " " for spaces
            }
    
            i = i + 1
        }
        return formatedCardNumber
    }
    

    and for shouldChangeCharactersIn:replacementString: a Swift 3.0 From Jayesh Miruliya Answer, put a separator between the group of four characters

     func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
        {
            if textField == CardNumTxt
            {
                let replacementStringIsLegal = string.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789").inverted) == nil
    
            if !replacementStringIsLegal
            {
                return false
            }
    
            let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
            let components = newString.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted)
    
            let decimalString = components.joined(separator: "") as NSString
            let length = decimalString.length
            let hasLeadingOne = length > 0 && decimalString.character(at: 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.append("1 ")
                index += 1
            }
            if length - index > 4 //magic number separata every four characters
            {
                let prefix = decimalString.substring(with: NSMakeRange(index, 4))
                formattedString.appendFormat("%@-", prefix)
                index += 4
            }
    
            if length - index > 4
            {
                let prefix = decimalString.substring(with: NSMakeRange(index, 4))
                formattedString.appendFormat("%@-", prefix)
                index += 4
            }
            if length - index > 4
            {
                let prefix = decimalString.substring(with: NSMakeRange(index, 4))
                formattedString.appendFormat("%@-", prefix)
                index += 4
            }
    
    
            let remainder = decimalString.substring(from: index)
            formattedString.append(remainder)
            textField.text = formattedString as String
            return false
            }
            else
            {
                return true
            }
        }
    

提交回复
热议问题