ios objective-c swift uitextfield credit-card

ios - Formateo de un UITextField para la entrada de la tarjeta de crédito como(xxxx xxxx xxxx xxxx)



objective-c swift (22)

Antes que nada, ¿estás seguro de que quieres hacer esto? Si bien el formato XXXX XXXX XXXX XXXX para números de tarjetas de crédito y débito es el más común, no es el único. Por ejemplo, las tarjetas American Express tienen números de 15 dígitos generalmente escritos en formato XXXX XXXXXX XXXXX , como este:

Incluso las tarjetas Visa pueden tener menos de 16 dígitos, y las tarjetas Maestro pueden tener más:

Es posible que desee considerar qué redes de tarjetas (como Visa, Maestro, etc.) está respaldando y en qué país o países se utilizará su aplicación, y tratar de averiguar qué formatos de números de tarjetas utilizan normalmente los emisores de esos países.

¿Has hecho eso o ya estás seguro de que estás haciendo lo correcto? Bien entonces...

La respuesta corta

Si está usando Swift, lea mi puerto de esta respuesta para Swift 4 y utilícelo en su lugar.

Si estás en Objective-C ...

En primer lugar, a su UITextFieldDelegate , agregue estas variables de instancia ...

NSString *previousTextFieldContent; UITextRange *previousSelection;

... y estos métodos:

// Version 1.2 // Source and explanation: http://stackoverflow.com/a/19161529/1709587 -(void)reformatAsCardNumber:(UITextField *)textField { // In order to make the cursor end up positioned correctly, we need to // explicitly reposition it after we inject spaces into the text. // targetCursorPosition keeps track of where the cursor needs to end up as // we modify the string, and at the end we set the cursor position to it. NSUInteger targetCursorPosition = [textField offsetFromPosition:textField.beginningOfDocument toPosition:textField.selectedTextRange.start]; NSString *cardNumberWithoutSpaces = [self removeNonDigits:textField.text andPreserveCursorPosition:&targetCursorPosition]; if ([cardNumberWithoutSpaces length] > 19) { // If the user is trying to enter more than 19 digits, we prevent // their change, leaving the text field in its previous state. // While 16 digits is usual, credit card numbers have a hard // maximum of 19 digits defined by ISO standard 7812-1 in section // 3.8 and elsewhere. Applying this hard maximum here rather than // a maximum of 16 ensures that users with unusual card numbers // will still be able to enter their card number even if the // resultant formatting is odd. [textField setText:previousTextFieldContent]; textField.selectedTextRange = previousSelection; return; } NSString *cardNumberWithSpaces = [self insertSpacesEveryFourDigitsIntoString:cardNumberWithoutSpaces andPreserveCursorPosition:&targetCursorPosition]; textField.text = cardNumberWithSpaces; UITextPosition *targetPosition = [textField positionFromPosition:[textField beginningOfDocument] offset:targetCursorPosition]; [textField setSelectedTextRange: [textField textRangeFromPosition:targetPosition toPosition:targetPosition] ]; } -(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { // Note textField''s current state before performing the change, in case // reformatTextField wants to revert it previousTextFieldContent = textField.text; previousSelection = textField.selectedTextRange; return YES; } /* Removes non-digits from the string, decrementing `cursorPosition` as appropriate so that, for instance, if we pass in `@"1111 1123 1111"` and a cursor position of `8`, the cursor position will be changed to `7` (keeping it between the ''2'' and the ''3'' after the spaces are removed). */ - (NSString *)removeNonDigits:(NSString *)string andPreserveCursorPosition:(NSUInteger *)cursorPosition { NSUInteger originalCursorPosition = *cursorPosition; NSMutableString *digitsOnlyString = [NSMutableString new]; for (NSUInteger i=0; i<[string length]; i++) { unichar characterToAdd = [string characterAtIndex:i]; if (isdigit(characterToAdd)) { NSString *stringToAdd = [NSString stringWithCharacters:&characterToAdd length:1]; [digitsOnlyString appendString:stringToAdd]; } else { if (i < originalCursorPosition) { (*cursorPosition)--; } } } return digitsOnlyString; } /* Inserts spaces into the string to format it as a credit card number, incrementing `cursorPosition` as appropriate so that, for instance, if we pass in `@"111111231111"` and a cursor position of `7`, the cursor position will be changed to `8` (keeping it between the ''2'' and the ''3'' after the spaces are added). */ - (NSString *)insertSpacesEveryFourDigitsIntoString:(NSString *)string andPreserveCursorPosition:(NSUInteger *)cursorPosition { NSMutableString *stringWithAddedSpaces = [NSMutableString new]; NSUInteger cursorPositionInSpacelessString = *cursorPosition; for (NSUInteger i=0; i<[string length]; i++) { if ((i>0) && ((i % 4) == 0)) { [stringWithAddedSpaces appendString:@" "]; if (i < cursorPositionInSpacelessString) { (*cursorPosition)++; } } unichar characterToAdd = [string characterAtIndex:i]; NSString *stringToAdd = [NSString stringWithCharacters:&characterToAdd length:1]; [stringWithAddedSpaces appendString:stringToAdd]; } return stringWithAddedSpaces; }

En segundo lugar, configure reformatCardNumber: para que se llame siempre que el campo de texto active un evento UIControlEventEditingChanged :

[yourTextField addTarget:yourTextFieldDelegate action:@selector(reformatAsCardNumber:) forControlEvents:UIControlEventEditingChanged];

(Por supuesto, tendrás que hacer esto en algún momento después de que tu campo de texto y su delegado hayan sido instanciados. Si estás usando storyboards, el método viewDidLoad de tu controlador de vista es un lugar apropiado.

Algunas explicaciones

Este es un problema engañosamente complicado. Dos cuestiones importantes que pueden no ser inmediatamente obvias (las dos respuestas anteriores publicadas no las tienen en cuenta):

  1. Hay más maneras para que el usuario interactúe con un campo de texto que simplemente escribiendo caracteres individuales al final de su entrada existente. También debe controlar adecuadamente al usuario agregando caracteres en el medio de la cadena, eliminando caracteres individuales, eliminando múltiples caracteres seleccionados y pegando en varios caracteres. Algunos enfoques más simples / más ingenuos para este problema no lograrán manejar algunas de estas interacciones correctamente. El caso más perverso es que un usuario pegue en varios caracteres en el medio de la cadena para reemplazar otros caracteres, y esta solución es lo suficientemente general como para manejar eso.
  2. No solo necesita volver a formatear correctamente el texto del campo de texto después de que el usuario lo modifique; también debe colocar el cursor del texto con sensatez. Los enfoques ingenuos al problema que no tienen esto en cuenta terminarán haciendo algo bastante tonto con el cursor de texto en algunos casos (como ponerlo al final del campo de texto después de que el usuario agregue un dígito en el medio del mismo) )

También hay otra trampa al acecho aquí, que es mucho menos importante que los dos anteriores, pero irritante para evitarlo:

  • Devolver NO desde textField: shouldChangeCharactersInRange: replacementString rompe el botón ''Cortar'' que el usuario obtiene cuando seleccionan texto en el campo de texto, por lo que no lo hago. Si devuelve NO de ese método, ''Cortar'' simplemente no actualiza el portapapeles, y no conozco ninguna solución o solución. Como resultado, tenemos que hacer el reformateo del campo de texto en un controlador UIControlEventEditingChanged lugar de (más obviamente) en shouldChangeCharactersInRange: sí mismo.

    Afortunadamente, los controladores de eventos de UIControl parecen ser llamados antes de que las actualizaciones de UI se vayan a la pantalla, por lo que este enfoque funciona bien.

La forma más simple y fácil de lidiar con el problema n . ° 1 (y el modo utilizado en el código anterior) es eliminar todos los espacios y reinsertarlos en las posiciones correctas cada vez que cambia el contenido del campo de texto, lo que nos ahorra la necesidad de qué tipo de manipulación de texto (una inserción, una eliminación o un reemplazo) está sucediendo y maneje las posibilidades de manera diferente.

Para lidiar con el problema n . ° 2 , hacemos un seguimiento de cómo cambia el índice deseado del cursor a medida que eliminamos los no dígitos y luego insertamos espacios. Esta es la razón por la que el código realiza estas manipulaciones de forma muy exhaustiva carácter por carácter utilizando NSMutableString , en lugar de utilizar los métodos de sustitución de cadenas de NSString .

También hay un montón de preguntas menores sobre cómo debe comportarse exactamente el campo de texto que no tiene respuestas obvias correctas:

  • Si el usuario intenta pegar algo que haría que el contenido del campo de texto exceda los 19 dígitos, debe insertarse el comienzo de la cadena pegada (hasta que se alcancen 19 dígitos) y el resto recortar, o no debe insertarse nada. ?
  • Si el usuario intenta eliminar un espacio individual colocando su cursor detrás de él y presionando la tecla de retroceso, si no pasa nada y el cursor permanece donde está, si el cursor se mueve un caracter izquierdo (colocándolo antes del espacio), o si el Se borrará el dígito a la izquierda del espacio como si el cursor ya estuviera a la izquierda del espacio?
  • Cuando el usuario escribe en el cuarto, octavo o duodécimo dígito, ¿debe insertarse inmediatamente un espacio y el cursor moverse después de él, o debe insertarse solo después de que el usuario escriba el quinto, noveno o decimotercer dígito?
  • Cuando el usuario elimina el primer dígito después de un espacio, si esto no hace que el espacio se elimine por completo, ¿esto debería conducir a que el cursor se coloque antes o después del espacio?

Probablemente cualquier respuesta a cualquiera de estas preguntas sea adecuada, pero las incluyo solo para dejar en claro que en realidad hay muchos casos especiales que tal vez desee considerar detenidamente aquí, si fue lo suficientemente obsesivo. En el código anterior, seleccioné las respuestas a estas preguntas que me parecieron razonables. Si tiene sentimientos fuertes sobre alguno de estos puntos que no son compatibles con la forma en que se comporta mi código, debería ser lo suficientemente fácil ajustarlo a sus necesidades.

Quiero formatear un UITextField para ingresar un número de tarjeta de crédito de forma tal que solo permita el ingreso de dígitos y automáticamente inserte espacios para que el número tenga el formato siguiente:

XXXX XXXX XXXX XXXX

¿Cómo puedo hacer esto?


Aquí hay una versión de Swift en caso de que sea útil para cualquiera que todavía esté buscando esta respuesta, pero usando Swift en lugar de Objective-C. Los conceptos siguen siendo los mismos independientemente.

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 }


Así que quise hacer esto con menos código, así que usé el código aquí y lo reutilicé un poco. Tenía dos campos en la pantalla, uno para el número y otro para la fecha de caducidad, así que lo hice más reutilizable.

Swift 3 respuesta alternativa

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()) } }


Creo que este es bueno:

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { NSLog(@"%@",NSStringFromRange(range)); // Only the 16 digits + 3 spaces if (range.location == 19) { return NO; } // Backspace if ([string length] == 0) return YES; if ((range.location == 4) || (range.location == 9) || (range.location == 14)) { NSString *str = [NSString stringWithFormat:@"%@ ",textField.text]; textField.text = str; } return YES; }


Debajo hay un puerto de Swift 4 en funcionamiento de la respuesta de Logicopolis (que a su vez es un puerto Swift 2 de mi propia respuesta aceptada en Objective-C). Sugiero revisar la respuesta aceptada si aún no lo ha hecho, ya que ayuda a explicar la motivación detrás de gran parte de este código.

Tenga en cuenta que la serie mínima de pasos necesarios para ver esto en acción es:

  1. Crea una nueva aplicación de vista única en Swift.
  2. En Main.storyboard , agregue un campo de texto .
  3. Convierta ViewController el delegado del campo de texto .
  4. Pegue el siguiente código en ViewController.swift .
  5. Conecte el IBOutlet al campo de texto .
  6. Ejecute su aplicación y escriba en el campo de texto .

import UIKit class ViewController: UIViewController, UITextFieldDelegate { private var previousTextFieldContent: String? private var previousSelection: UITextRange? @IBOutlet var yourTextField: UITextField!; override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib yourTextField.addTarget(self, action: #selector(reformatAsCardNumber), for: .editingChanged) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { previousTextFieldContent = textField.text; previousSelection = textField.selectedTextRange; return true } @objc 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 = self.removeNonDigits(string: text, andPreserveCursorPosition: &targetCursorPosition) } if cardNumberWithoutSpaces.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 Swift.stride(from: 0, to: string.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 Swift.stride(from: 0, to: string.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 } }

Adaptar esto a otras situaciones, como que tu delegado no sea una ViewController, se deja como un ejercicio para el lector.

(Me disculpo si el código es unidiomático, ha sido portado dos veces y nunca he hecho ningún desarrollo Swift además de esto).


Defina el siguiente método y llámelo en delegados UITextfield o donde sea necesario

-(NSString*)processString :(NSString*)yourString { if(yourString == nil){ return @""; } int stringLength = (int)[yourString length]; int len = 4; // Length after which you need to place added character NSMutableString *str = [NSMutableString string]; int i = 0; for (; i < stringLength; i+=len) { NSRange range = NSMakeRange(i, len); [str appendString:[yourString substringWithRange:range]]; if(i!=stringLength -4){ [str appendString:@" "]; //If required string format is XXXX-XXXX-XXXX-XXX then just replace [str appendString:@"-"] } } if (i < [str length]-1) { // add remaining part [str appendString:[yourString substringFromIndex:i]]; } //Returning required string return str; }


Probablemente pueda optimizar mi código o podría haber una manera más fácil, pero este código debería funcionar:

-(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; }


Sin embargo, otra versión de la respuesta aceptada en Swift 2 ...

Asegúrese de tenerlos en su instancia de delegado:

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

Y también asegúrese de que su campo de texto llame a reformatAsCardNumber:

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

Tu delegado de campo de texto tendrá que hacer esto:

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

Por último, incluye los siguientes métodos:

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 }


La solución Swift 3 usando Fawkes responde como básica. Se agregó compatibilidad con formato de tarjeta Amex. Se agregó una reforma cuando el tipo de tarjeta cambió.

Primero crea una nueva clase con este código:

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 } }

En su ViewControllerClass agregue esta función

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) }

A continuación, agregue el objetivo a su campo de texto

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

Registre la nueva variable y el tipo de tarjeta enviado

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

Gracias Fawkes por su código!


Swift 3.2

Poca corrección en la respuesta @Lucas y código de trabajo en swift 3.2. También elimina el carácter de espacio automáticamente.

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 }


Encontré un GIST en Github que hace exactamente lo que necesito en Swift3 ( https://gist.github.com/nunogoncalves/6a8b4b21f4f69e0fc050190df96a1e56 )

Implementado haciendo ->

if creditCardNumberTextView.text?.characters.first == "3" { let validator = Validator(cardType: .americanExpress, value: self.creditCardNumberTextView.text!).test() if validator == true { } else { } }

Funciona maravillosamente en la aplicación que estoy trabajando que usa tarjetas de crédito.


Por favor revise la siguiente solución, está funcionando bien para mí-

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 }


Swift 3 solución basada en la solución Objective-C de Mark Amery :

  1. Implementar métodos de acción y delegar:

    textField.addTarget(self, action: #selector(reformatAsCardNumber(_:)) textField.delegate = self

  2. Métodos de Delegado de TextField y otros métodos:

    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 }


Utilice una forma simple de tarjeta de crédito / ** Vea el uso de la muestra: ### 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 }

Espero que esto sea útil para ti.


Aquí hay una copia rápida de la respuesta aceptada en caso de que alguien la necesite. Básicamente es una clase de contenedor. No he dedicado demasiado tiempo a optimizarlo, pero está listo para usar.

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 } }


Estas respuestas son demasiado código para mí. Aquí hay una solución en 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 } } }

Ahora solo ponga un IBAction en su controlador de vista:

@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) }


Modifiqué la respuesta de @ilesh para que solo muestre los últimos 4 dígitos sin importar cuál sea la duración. También para ignorar el espacio y "-" caracteres. De esta manera, si tenemos un número con el formato 0000 - 0000 - 0000 - 0000, muestra 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 }


Para lograr el objetivo de formatear el texto ingresado en el campo de texto de esta manera XXXX XXXX XXXX XXXX es importante tener en cuenta algunas cosas importantes. Además del hecho de que el número de tarjeta de 16 dígitos separado cada cuatro dígitos es el formato utilizado más común, hay tarjetas con 15 dígitos (AmEx formateado XXXX XXXXXX XXXXX) y otras con 13 dígitos o incluso con 19 dígitos ( https: // en. wikipedia.org/wiki/Payment_card_number ). Otra cosa importante que debe considerar es configurar textField para permitir solo dígitos, configurar el tipo de teclado ya que numberPad es un buen comienzo, pero es conveniente implementar un método que asegure la entrada.

Un punto de partida es decidir cuándo desea formatear el número, mientras el usuario ingresa el número o cuando el usuario abandona el campo de texto. En el caso de que desee formatear cuando el usuario abandone el campo textField, es conveniente utilizar el método textFieldDidEndEditing (_ :) del delegado, tome el contenido del campo textField y formatéelo.

En el caso de que mientras el usuario ingresa el número es útil el método de delegado textField (_: shouldChangeCharactersIn: replacementString :) que se invoca cada vez que cambia el texto actual.

En ambos casos, todavía hay un problema, averiguar cuál es el formato correcto para el número ingresado, en mi humilde opinión y en base a todos los números que he visto, solo hay dos formatos principales: el formato Amex con 15 dígitos descrito anteriormente y el formatear el número de tarjeta de grupo cada cuatro dígitos a los que no les importa cuántos dígitos hay, siendo este caso una regla genérica, por ejemplo, una tarjeta con 13 dígitos se formateará XXXXX XXXX XXXX X y con 19 dígitos se verá así XXXX XXXX XXXX XXXX XXX, esto funcionará para los casos más comunes (16 dígitos) y para los demás también. Para que pueda averiguar cómo administrar el caso AmEx con el mismo algoritmo que se muestra a continuación, juegue con los números mágicos.

Utilicé un RegEx para garantizar que una tarjeta de 15 dígitos sea un expreso estadounidense, en el caso de otros formatos particulares

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

Recomiendo utilizar el RegEx específico, que es útil para identificar al emisor y calcular cuántos dígitos se deben aceptar.

Ahora mi enfoque rápido de la solución con textFieldDidEndEditing es

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 }

y para shouldChangeCharactersIn: replacementString: a Swift 3.0 De Jayesh Miruliya Answer, coloque un separador entre el grupo de cuatro caracteres

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 } }


Vea esta solución. Encontré en Autorize.net SDK Example.

Haga su UITextFieldtipo de teclado a Numeric.

Enmascara números de tarjeta de crédito con ''X'' y al agregar espacios, hará ''XXXX XXXX XXXX 1234''formato.

En el archivo Header .h

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

En archivo .m

- (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; } }


aquí está la modificación de la respuesta de @sleeping_giant para swift. Esta solución formatea el texto en formato ''xxxx-xxxx-xxxx-xxxx-xxxx'' y deja de aceptar cualquier número que esté más allá de ese rango.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if string == ""{ return true } //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 print(range.location) if range.location > 23 { return false } var originalText = textField.text let replacementText = string.replacingOccurrences(of: "-", with: "") //Verify entered text is a numeric value let digits = NSCharacterSet.decimalDigits for char in replacementText.unicodeScalars { if !(digits as NSCharacterSet).longCharacterIsMember(char.value) { return false } } //Put an empty space after every 4 places if (originalText?.characters.count)! > 0 { if (originalText?.characters.count)! < 5 && (originalText?.characters.count)! % 4 == 0{ originalText?.append("-") }else if(((originalText?.characters.count)! + 1) % 5 == 0){ originalText?.append("-") } } textField.text = originalText return true }


en mi caso, tenemos que formatear el número iban. Creo que el siguiente bloque de código te ayuda

En primer lugar, compruebe que el valor entrante del usuario sea válido

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

En segundo lugar, puede ver el método formateado de iban justo como a continuación. Nuestra formataron 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; }


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 "-" cualquier otro que elijas