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

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

    Swift 3 solution based upon Mark Amery's Objective-C solution:

    1. Implement action and delegate methods:

      textField.addTarget(self, action: #selector(reformatAsCardNumber(_:))
      textField.delegate = self
      
    2. TextField Delegate methods and other methods:

      func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
          previousTextFieldContent = textField.text;
          previousSelection = textField.selectedTextRange;
          return true
      }
      
      func reformatAsCardNumber(_ textField: UITextField) {
          var targetCursorPosition = 0
          if let startPosition = textField.selectedTextRange?.start {
              targetCursorPosition = textField.offset(from:textField.beginningOfDocument, to: startPosition)
          }
      
          var cardNumberWithoutSpaces = ""
          if let text = textField.text {
              cardNumberWithoutSpaces = removeNonDigits(string: text, andPreserveCursorPosition: &targetCursorPosition)
          }
      
          if cardNumberWithoutSpaces.characters.count > 19 {
              textField.text = previousTextFieldContent
              textField.selectedTextRange = previousSelection
              return
          }
      
          let cardNumberWithSpaces = self.insertSpacesEveryFourDigitsIntoString(string: cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
          textField.text = cardNumberWithSpaces
      
          if let targetPosition = textField.position(from: textField.beginningOfDocument, offset: targetCursorPosition) {
              textField.selectedTextRange = textField.textRange(from: targetPosition, to: targetPosition)
          }
      }
      
      func removeNonDigits(string: String, andPreserveCursorPosition cursorPosition: inout Int) -> String {
          var digitsOnlyString = ""
          let originalCursorPosition = cursorPosition
      
          for i in stride(from: 0, to: string.characters.count, by: 1) {
              let characterToAdd =  string[string.index(string.startIndex, offsetBy: i)]
              if characterToAdd >= "0" && characterToAdd <= "9" {
                  digitsOnlyString.append(characterToAdd)
              }
              else if i < originalCursorPosition {
                  cursorPosition -= 1
              }
          }
      
          return digitsOnlyString
      }
      
      func insertSpacesEveryFourDigitsIntoString(string: String, andPreserveCursorPosition cursorPosition: inout Int) -> String {
          var stringWithAddedSpaces = ""
          let cursorPositionInSpacelessString = cursorPosition
      
          for i in stride(from: 0, to: string.characters.count, by: 1) {
              if i > 0 && (i % 4) == 0 {
                  stringWithAddedSpaces.append(" ")
                  if i < cursorPositionInSpacelessString {
                      cursorPosition += 1
                  }
              }
              let characterToAdd = string[string.index(string.startIndex, offsetBy: i)]
              stringWithAddedSpaces.append(characterToAdd)
          }
      
          return stringWithAddedSpaces
      }
      
    0 讨论(0)
  • 2020-11-28 01:47

    i modified @ilesh answer so it only shows the last 4 digits no matter what the lenght is. Also to ignore the space and "-" chars. This way, if we have a number with the format 0000 - 0000 - 0000 - 0000 it displays XXXX - XXXX - XXXX - 0000

    func setStringAsCardNumberWithSartNumber(Number:Int,withString str:String) -> String{
        let arr = str.characters
        var CrediteCard : String = ""
        let len = str.characters.count-4
        if arr.count > (Number + len) {
            for (index, element ) in arr.enumerated(){
                if index >= Number && index < (Number + len) && element != "-" && element != " " {
                    CrediteCard = CrediteCard + String("X")
                }else{
                    CrediteCard = CrediteCard + String(element)
                }
            }
            return CrediteCard
        }else{
            print("\(Number) plus \(len) are grether than strings chatarter \(arr.count)")
        }
        print("\(CrediteCard)")
        return str
    }
    
    0 讨论(0)
  • 2020-11-28 01:48

    Swift 5.1, Xcode 11

    After trying many solutions, I faced issues such as setting correct cursor position and formating as per need, I finally found a solution after combining 2 posts (https://stackoverflow.com/a/38838740/10579134, https://stackoverflow.com/a/45297778/10579134)

    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 == yourTextField  {
    
            textField.setText(to: currentText.grouping(every: 4, with: "-"), preservingCursor: true)
    
            return false
        }
        return true
    }
    

    And adding this extension

    extension UITextField {
    
    public func setText(to newText: String, preservingCursor: Bool) {
        if preservingCursor {
            let cursorPosition = offset(from: beginningOfDocument, to: selectedTextRange!.start) + newText.count - (text?.count ?? 0)
            text = newText
            if let newPosition = self.position(from: beginningOfDocument, offset: cursorPosition) {
                selectedTextRange = textRange(from: newPosition, to: newPosition)
            }
        }
        else {
            text = newText
        }
    }
    
    0 讨论(0)
  • 2020-11-28 01:49

    Please use simple form of credite card /** See sample usage: ### let str = "41111111111111111"

     let x = yourClassname.setStringAsCardNumberWithSartNumber(4, withString: str!, withStrLenght: 8)
    
     ### output:- 4111XXXXXXXX1111
    
     let x = yourClassname.setStringAsCardNumberWithSartNumber(0, withString: str!, withStrLenght: 12)
    
     ### output: - XXXXXXXXXXXX1111
    
     */
    func setStringAsCardNumberWithSartNumber(Number:Int,withString str:String ,withStrLenght len:Int ) -> String{
        //let aString: String = "41111111111111111"
        let arr = str.characters
        var CrediteCard : String = ""
        if arr.count > (Number + len) {
            for (index, element ) in arr.enumerate(){
                if index >= Number && index < (Number + len) {
                    CrediteCard = CrediteCard + String("X")
                }else{
                    CrediteCard = CrediteCard + String(element)
                }
            }
          return CrediteCard
        }else{
                print("\(Number) plus \(len) are grether than strings chatarter \(arr.count)")
        }
        print("\(CrediteCard)")
        return str
    }
    

    I hope this is helpful to you.

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

    Yet another version of the accepted answer in Swift 2...

    Ensure you have these in your delegate instance:

    private var previousTextFieldContent: String?
    private var previousSelection: UITextRange?
    

    And also ensure that your text field calls reformatAsCardNumber:

    textField.addTarget(self, action: #selector(reformatAsCardNumber(_:)), forControlEvents: .EditingChanged)
    

    You text field delegate will need to do this:

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        previousTextFieldContent = textField.text;
        previousSelection = textField.selectedTextRange;
        return true
    }
    

    Lastly include the following methods:

    func reformatAsCardNumber(textField: UITextField) {
        var targetCursorPosition = 0
        if let startPosition = textField.selectedTextRange?.start {
            targetCursorPosition = textField.offsetFromPosition(textField.beginningOfDocument, toPosition: startPosition)
        }
    
        var cardNumberWithoutSpaces = ""
        if let text = textField.text {
            cardNumberWithoutSpaces = self.removeNonDigits(text, andPreserveCursorPosition: &targetCursorPosition)
        }
    
        if cardNumberWithoutSpaces.characters.count > 19 {
            textField.text = previousTextFieldContent
            textField.selectedTextRange = previousSelection
            return
        }
    
        let cardNumberWithSpaces = self.insertSpacesEveryFourDigitsIntoString(cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
        textField.text = cardNumberWithSpaces
    
        if let targetPosition = textField.positionFromPosition(textField.beginningOfDocument, offset: targetCursorPosition) {
            textField.selectedTextRange = textField.textRangeFromPosition(targetPosition, toPosition: targetPosition)
        }
    }
    
    func removeNonDigits(string: String, inout andPreserveCursorPosition cursorPosition: Int) -> String {
        var digitsOnlyString = ""
        let originalCursorPosition = cursorPosition
    
        for i in 0.stride(to: string.characters.count, by: 1) {
            let characterToAdd = string[string.startIndex.advancedBy(i)]
            if characterToAdd >= "0" && characterToAdd <= "9" {
                digitsOnlyString.append(characterToAdd)
            }
            else if i < originalCursorPosition {
                cursorPosition -= 1
            }
        }
    
        return digitsOnlyString
    }
    
    func insertSpacesEveryFourDigitsIntoString(string: String, inout andPreserveCursorPosition cursorPosition: Int) -> String {
        var stringWithAddedSpaces = ""
        let cursorPositionInSpacelessString = cursorPosition
    
        for i in 0.stride(to: string.characters.count, by: 1) {
            if i > 0 && (i % 4) == 0 {
                stringWithAddedSpaces.appendContentsOf(" ")
                if i < cursorPositionInSpacelessString {
                    cursorPosition += 1
                }
            }
            let characterToAdd = string[string.startIndex.advancedBy(i)]
            stringWithAddedSpaces.append(characterToAdd)
        }
    
        return stringWithAddedSpaces
    }
    
    0 讨论(0)
  • 2020-11-28 01:52

    Here's a swift copy of the accepted answer in case somebody needs it. It is basically a wrapper class. I haven't spent too much time on optimising it, but it's ready for use.

    var creditCardFormatter : CreditCardFormatter
    {
        return CreditCardFormatter.sharedInstance
    }
    
    class CreditCardFormatter : NSObject
    {
        static let sharedInstance : CreditCardFormatter = CreditCardFormatter()
    
        func formatToCreditCardNumber(textField : UITextField, withPreviousTextContent previousTextContent : String?, andPreviousCursorPosition previousCursorSelection : UITextRange?)
        {
            if let selectedRangeStart = textField.selectedTextRange?.start, textFieldText = textField.text
            {
                var targetCursorPosition : UInt = UInt(textField.offsetFromPosition(textField.beginningOfDocument, toPosition: selectedRangeStart))
    
                let cardNumberWithoutSpaces : String = removeNonDigitsFromString(textFieldText, andPreserveCursorPosition: &targetCursorPosition)
    
                if cardNumberWithoutSpaces.characters.count > 19
                {
                    textField.text = previousTextContent
                    textField.selectedTextRange = previousCursorSelection
                    return
                }
    
                let cardNumberWithSpaces : String = insertSpacesIntoEvery4DigitsIntoString(cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
    
                textField.text = cardNumberWithSpaces
    
                if let finalCursorPosition = textField.positionFromPosition(textField.beginningOfDocument, offset: Int(targetCursorPosition))
                {
                    textField.selectedTextRange = textField.textRangeFromPosition(finalCursorPosition, toPosition: finalCursorPosition)
                }
            }
        }
    
        func removeNonDigitsFromString(string : String,inout andPreserveCursorPosition cursorPosition : UInt) -> String
        {
            var digitsOnlyString : String = ""
    
            for index in 0.stride(to: string.characters.count, by: 1)
            {
                let charToAdd : Character = Array(string.characters)[index]
    
                if isDigit(charToAdd)
                {
                    digitsOnlyString.append(charToAdd)
                }
                else
                {
                    if index < Int(cursorPosition)
                    {
                        cursorPosition -= 1
                    }
                }
            }
    
            return digitsOnlyString
        }
    
        private func isDigit(character : Character) -> Bool
        {
            return "\(character)".containsOnlyDigits()
        }
    
        func insertSpacesIntoEvery4DigitsIntoString(string : String, inout andPreserveCursorPosition cursorPosition : UInt) -> String
        {
            var stringWithAddedSpaces : String = ""
    
            for index in 0.stride(to: string.characters.count, by: 1)
            {
                if index != 0 && index % 4 == 0
                {
                    stringWithAddedSpaces += " "
    
                    if index < Int(cursorPosition)
                    {
                        cursorPosition += 1
                    }
                }
    
                let characterToAdd : Character = Array(string.characters)[index]
    
                stringWithAddedSpaces.append(characterToAdd)
            }
    
            return stringWithAddedSpaces
        }
    
    }
    
    extension String
    {
        func containsOnlyDigits() -> Bool
        {
            let notDigits : NSCharacterSet = NSCharacterSet.decimalDigitCharacterSet().invertedSet
    
            if (rangeOfCharacterFromSet(notDigits, options: NSStringCompareOptions.LiteralSearch, range: nil) == nil)
            {
                return true
            }
    
            return false
        }
    }
    
    0 讨论(0)
提交回复
热议问题