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

前端 未结 28 2077
长情又很酷
长情又很酷 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:35

    So I wanted to this with less code, so I used the code here and repurposed it a little bit. I had two fields in the screen, one for the number and one for the expiry date, so I made it more reusable.

    Swift 3 alternate answer

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let currentText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) else { return true }
    
        if textField == cardNumberTextField {
            textField.text = currentText.grouping(every: 4, with: " ")
            return false
        }
        else { // Expiry Date Text Field
            textField.text = currentText.grouping(every: 2, with: "/")
            return false
        }
    }
    
    extension String {
        func grouping(every groupSize: String.IndexDistance, with separator: Character) -> String {
           let cleanedUpCopy = replacingOccurrences(of: String(separator), with: "")
           return String(cleanedUpCopy.characters.enumerated().map() {
                $0.offset % groupSize == 0 ? [separator, $0.element] : [$0.element]
           }.joined().dropFirst())
        }
    }
    
    0 讨论(0)
  • 2020-11-28 01:35

    Here is a Swift version in case this is useful to anyone still looking for this answer but using Swift instead of Objective-C. The concepts are still the same regardless.

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
    {
        //range.length will be greater than 0 if user is deleting text - allow it to replace
        if range.length > 0
        {
            return true
        }
    
        //Don't allow empty strings
        if string == " "
        {
            return false
        }
    
        //Check for max length including the spacers we added
        if range.location == 20
        {
            return false
        }
    
        var originalText = textField.text
        let replacementText = string.stringByReplacingOccurrencesOfString(" ", withString: "")
    
        //Verify entered text is a numeric value
        let digits = NSCharacterSet.decimalDigitCharacterSet()
        for char in replacementText.unicodeScalars
        {
            if !digits.longCharacterIsMember(char.value)
            {
                return false
            }
        }
    
        //Put an empty space after every 4 places
        if originalText!.length() % 5 == 0
        {
            originalText?.appendContentsOf(" ")
            textField.text = originalText
        }
    
        return true
    }
    
    0 讨论(0)
  • 2020-11-28 01:35

    Create new swift file and paste below code, change text field class to VSTextField

    import UIKit
    
    public enum TextFieldFormatting {
        case uuid
        case socialSecurityNumber
        case phoneNumber
        case custom
        case noFormatting
    }
    
    public class VSTextField: UITextField {
    
        /**
         Set a formatting pattern for a number and define a replacement string. For example: If formattingPattern would be "##-##-AB-##" and
         replacement string would be "#" and user input would be "123456", final string would look like "12-34-AB-56"
         */
        public func setFormatting(_ formattingPattern: String, replacementChar: Character) {
            self.formattingPattern = formattingPattern
            self.replacementChar = replacementChar
            self.formatting = .custom
        }
    
        /**
         A character which will be replaced in formattingPattern by a number
         */
        public var replacementChar: Character = "*"
    
        /**
         A character which will be replaced in formattingPattern by a number
         */
        public var secureTextReplacementChar: Character = "\u{25cf}"
    
        /**
         True if input number is hexadecimal eg. UUID
         */
        public var isHexadecimal: Bool {
            return formatting == .uuid
        }
    
        /**
         Max length of input string. You don't have to set this if you set formattingPattern.
         If 0 -> no limit.
         */
        public var maxLength = 0
    
        /**
         Type of predefined text formatting. (You don't have to set this. It's more a future feature)
         */
        public var formatting : TextFieldFormatting = .noFormatting {
            didSet {
                switch formatting {
    
                case .socialSecurityNumber:
                    self.formattingPattern = "***-**-****"
                    self.replacementChar = "*"
    
                case .phoneNumber:
                    self.formattingPattern = "***-***-****"
                    self.replacementChar = "*"
    
                case .uuid:
                    self.formattingPattern = "********-****-****-****-************"
                    self.replacementChar = "*"
    
                default:
                    self.maxLength = 0
                }
            }
        }
    
        /**
         String with formatting pattern for the text field.
         */
        public var formattingPattern: String = "" {
            didSet {
                self.maxLength = formattingPattern.count
            }
        }
    
        /**
         Provides secure text entry but KEEPS formatting. All digits are replaced with the bullet character \u{25cf} .
         */
        public var formatedSecureTextEntry: Bool {
            set {
                _formatedSecureTextEntry = newValue
                super.isSecureTextEntry = false
            }
    
            get {
                return _formatedSecureTextEntry
            }
        }
    
        override public var text: String! {
            set {
                super.text = newValue
                textDidChange() // format string properly even when it's set programatically
            }
    
            get {
                if case .noFormatting = formatting {
                    return super.text
                } else {
                    // Because the UIControl target action is called before NSNotificaion (from which we fire our custom formatting), we need to
                    // force update finalStringWithoutFormatting to get the latest text. Otherwise, the last character would be missing.
                    textDidChange()
                    return finalStringWithoutFormatting
                }
            }
        }
    
        required public init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            registerForNotifications()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            registerForNotifications()
        }
    
        deinit {
            NotificationCenter.default.removeObserver(self)
        }
    
        /**
         Final text without formatting characters (read-only)
         */
        public var finalStringWithoutFormatting : String {
            return _textWithoutSecureBullets.keepOnlyDigits(isHexadecimal: isHexadecimal)
        }
    
        // MARK: - INTERNAL
        fileprivate var _formatedSecureTextEntry = false
    
        // if secureTextEntry is false, this value is similar to self.text
        // if secureTextEntry is true, you can find final formatted text without bullets here
        fileprivate var _textWithoutSecureBullets = ""
    
        fileprivate func registerForNotifications() {
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(VSTextField.textDidChange),
                                                   name: NSNotification.Name(rawValue: "UITextFieldTextDidChangeNotification"),
                                                   object: self)
        }
    
        @objc public func textDidChange() {
            var superText: String { return super.text ?? "" }
    
            // TODO: - Isn't there more elegant way how to do this?
            let currentTextForFormatting: String
    
            if superText.count > _textWithoutSecureBullets.count {
                currentTextForFormatting = _textWithoutSecureBullets + superText[superText.index(superText.startIndex, offsetBy: _textWithoutSecureBullets.count)...]
            } else if superText.count == 0 {
                _textWithoutSecureBullets = ""
                currentTextForFormatting = ""
            } else {
                currentTextForFormatting = String(_textWithoutSecureBullets[..<_textWithoutSecureBullets.index(_textWithoutSecureBullets.startIndex, offsetBy: superText.count)])
            }
    
            if formatting != .noFormatting && currentTextForFormatting.count > 0 && formattingPattern.count > 0 {
                let tempString = currentTextForFormatting.keepOnlyDigits(isHexadecimal: isHexadecimal)
    
                var finalText = ""
                var finalSecureText = ""
    
                var stop = false
    
                var formatterIndex = formattingPattern.startIndex
                var tempIndex = tempString.startIndex
    
                while !stop {
                    let formattingPatternRange = formatterIndex ..< formattingPattern.index(formatterIndex, offsetBy: 1)
                    if formattingPattern[formattingPatternRange] != String(replacementChar) {
    
                        finalText = finalText + formattingPattern[formattingPatternRange]
                        finalSecureText = finalSecureText + formattingPattern[formattingPatternRange]
    
                    } else if tempString.count > 0 {
    
                        let pureStringRange = tempIndex ..< tempString.index(tempIndex, offsetBy: 1)
    
                        finalText = finalText + tempString[pureStringRange]
    
                        // we want the last number to be visible
                        if tempString.index(tempIndex, offsetBy: 1) == tempString.endIndex {
                            finalSecureText = finalSecureText + tempString[pureStringRange]
                        } else {
                            finalSecureText = finalSecureText + String(secureTextReplacementChar)
                        }
    
                        tempIndex = tempString.index(after: tempIndex)
                    }
    
                    formatterIndex = formattingPattern.index(after: formatterIndex)
    
                    if formatterIndex >= formattingPattern.endIndex || tempIndex >= tempString.endIndex {
                        stop = true
                    }
                }
    
                _textWithoutSecureBullets = finalText
    
                let newText = _formatedSecureTextEntry ? finalSecureText : finalText
                if newText != superText {
                    super.text = _formatedSecureTextEntry ? finalSecureText : finalText
                }
            }
    
            // Let's check if we have additional max length restrictions
            if maxLength > 0 {
                if superText.count > maxLength {
                    super.text = String(superText[..<superText.index(superText.startIndex, offsetBy: maxLength)])
                    _textWithoutSecureBullets = String(_textWithoutSecureBullets[..<_textWithoutSecureBullets.index(_textWithoutSecureBullets.startIndex, offsetBy: maxLength)])
                }
            }
        }
    }
    
    
    extension String {
    
        func keepOnlyDigits(isHexadecimal: Bool) -> String {
            let ucString = self.uppercased()
            let validCharacters = isHexadecimal ? "0123456789ABCDEF" : "0123456789"
            let characterSet: CharacterSet = CharacterSet(charactersIn: validCharacters)
            let stringArray = ucString.components(separatedBy: characterSet.inverted)
            let allNumbers = stringArray.joined(separator: "")
            return allNumbers
        }
    }
    
    
    // Helpers
    fileprivate func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
        switch (lhs, rhs) {
        case let (l?, r?):
            return l < r
        case (nil, _?):
            return true
        default:
            return false
        }
    }
    
    fileprivate func > <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
        switch (lhs, rhs) {
        case let (l?, r?):
            return l > r
        default:
            return rhs < lhs
        }
    }
    

    More uses will be found on below link

    Thanks to the guy who provided the great solution for formatting text in UITextField.

    http://vojtastavik.com/2015/03/29/real-time-formatting-in-uitextfield-swift-basics/

    https://github.com/VojtaStavik/VSTextField

    Working great for me.

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

    Please check bellow solution, its working fine for me-

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    
            let subString = (textField.text as! NSString).substringWithRange(range)
            if subString == " " && textField == cardNumberTextfield
            {
                return false     // user should not be able to delete space from card field
            }
            else if string == ""
            {
                return true      // user can delete any digit
            }
    
    
            // Expiry date formatting
    
            if textField == expiryDateTextfield
            {
                let str = textField.text! + string
    
                if str.length == 2 && Int(str) > 12
                {
                    return false                  // Month should be <= 12
                }
                else if str.length == 2
                {
                    textField.text = str+"/"      // append / after month
                    return false
                }
                else if str.length > 5
                {
                    return false                  // year should be in yy format
                }
            }
    
    
    
            // Card number formatting
    
            if textField == cardNumberTextfield
            {
                let str = textField.text! + string
    
                let stringWithoutSpace = str.stringByReplacingOccurrencesOfString(" ", withString: "")
    
                if stringWithoutSpace.length % 4 == 0 && (range.location == textField.text?.length)
                {
                    if stringWithoutSpace.length != 16
                    {
                        textField.text = str+" "    // add space after every 4 characters
                    }
                    else
                    {
                        textField.text = str       // space should not be appended with last digit
                    }
    
                    return false
                }
                else if str.length > 19
                {
                    return false
                }
            }
    
    
    
            return true
        }
    
    0 讨论(0)
  • 2020-11-28 01:38

    in my case, we have to formating iban number. i think, the below code block help you

    Firstly, check the user enterted value is valid

    -(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
    
        if(textField == self.ibanTextField){
    
               BOOL shouldChange =  ([Help checkTextFieldForIBAN:[NSString stringWithFormat:@"%@%@",textField.text,string]]);
     }
    }
    

    Secondly, you can see iban formated method just like below. Our iban formated begin 2 letter.

    +(BOOL)checkTextFieldForIBAN:(NSString*)string{
    
        string = [string stringByReplacingOccurrencesOfString:@" " withString:@""];
    
        if ([string length] <= 26) {
    
            if ([string length] > 2) {
    
                if ([self isLetter:[string substringToIndex:2]]) {
    
                    if ([self isInteger:[string substringFromIndex:2]])
                        return YES;
                    else
                        return NO;
    
                }else {
    
                    return NO;
                }
            }else{
    
                return [self isLetter:string];
            }
    
        }
        else {
    
            return NO;
        }
    
        return YES;
    }
    
    0 讨论(0)
  • 2020-11-28 01:40

    In Swift 5 :

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            if textField == cardNumberTextField {
                return formatCardNumber(textField: textField, shouldChangeCharactersInRange: range, replacementString: string)
            }
            return true
        }
    
    
        func formatCardNumber(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
            if textField == cardNumberTextField {
                let replacementStringIsLegal = string.rangeOfCharacter(from: NSCharacterSet(charactersIn: "0123456789").inverted) == nil
    
                if !replacementStringIsLegal {
                    return false
                }
    
                let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
                let components = newString.components(separatedBy: NSCharacterSet(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 {
                    let prefix = decimalString.substring(with: NSRange(location: index, length: 4))
                    formattedString.appendFormat("%@ ", prefix)
                    index += 4
                }
    
                if length - index > 4 {
                    let prefix = decimalString.substring(with: NSRange(location: index, length: 4))
                    formattedString.appendFormat("%@ ", prefix)
                    index += 4
                }
                if length - index > 4 {
                    let prefix = decimalString.substring(with: NSRange(location: index, length: 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
            }
        }
    
    0 讨论(0)
提交回复
热议问题