Ich möchte eine UITextField
für die Eingabe einer Kreditkartennummer so formatieren, dass nur Ziffern eingegeben werden können und Leerzeichen automatisch eingefügt werden, sodass die Nummer folgendermaßen formatiert wird:
XXXX XXXX XXXX XXXX
Wie kann ich das machen?
ios
objective-c
swift
uitextfield
credit-card
Kann Aksoy
quelle
quelle
Antworten:
Wenn Sie Swift verwenden, lesen Sie meinen Port dieser Antwort für Swift 4 und verwenden Sie diesen stattdessen.
Wenn Sie in Objective-C sind ...
Fügen Sie zunächst Ihre
UITextFieldDelegate
Instanzvariablen hinzu ...NSString *previousTextFieldContent; UITextRange *previousSelection;
... und diese Methoden:
// Version 1.3 // 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 insertCreditCardSpaces: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; } /* Detects the card number format from the prefix, then 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 *)insertCreditCardSpaces:(NSString *)string andPreserveCursorPosition:(NSUInteger *)cursorPosition { // Mapping of card prefix to pattern is taken from // https://baymard.com/checkout-usability/credit-card-patterns // UATP cards have 4-5-6 (XXXX-XXXXX-XXXXXX) format bool is456 = [string hasPrefix: @"1"]; // These prefixes reliably indicate either a 4-6-5 or 4-6-4 card. We treat all // these as 4-6-5-4 to err on the side of always letting the user type more // digits. bool is465 = [string hasPrefix: @"34"] || [string hasPrefix: @"37"] || // Diners Club [string hasPrefix: @"300"] || [string hasPrefix: @"301"] || [string hasPrefix: @"302"] || [string hasPrefix: @"303"] || [string hasPrefix: @"304"] || [string hasPrefix: @"305"] || [string hasPrefix: @"309"] || [string hasPrefix: @"36"] || [string hasPrefix: @"38"] || [string hasPrefix: @"39"]; // In all other cases, assume 4-4-4-4-3. // This won't always be correct; for instance, Maestro has 4-4-5 cards // according to https://baymard.com/checkout-usability/credit-card-patterns, // but I don't know what prefixes identify particular formats. bool is4444 = !(is456 || is465); NSMutableString *stringWithAddedSpaces = [NSMutableString new]; NSUInteger cursorPositionInSpacelessString = *cursorPosition; for (NSUInteger i=0; i<[string length]; i++) { bool needs465Spacing = (is465 && (i == 4 || i == 10 || i == 15)); bool needs456Spacing = (is456 && (i == 4 || i == 9 || i == 15)); bool needs4444Spacing = (is4444 && i > 0 && (i % 4) == 0); if (needs465Spacing || needs456Spacing || needs4444Spacing) { [stringWithAddedSpaces appendString:@" "]; if (i < cursorPositionInSpacelessString) { (*cursorPosition)++; } } unichar characterToAdd = [string characterAtIndex:i]; NSString *stringToAdd = [NSString stringWithCharacters:&characterToAdd length:1]; [stringWithAddedSpaces appendString:stringToAdd]; } return stringWithAddedSpaces; }
Zweitens festlegen
reformatCardNumber:
, dass aufgerufen wird, wenn das Textfeld einUIControlEventEditingChanged
Ereignis auslöst:[yourTextField addTarget:yourTextFieldDelegate action:@selector(reformatAsCardNumber:) forControlEvents:UIControlEventEditingChanged];
(Natürlich müssen Sie dies irgendwann tun, nachdem Ihr Textfeld und sein Delegat instanziiert wurden. Wenn Sie Storyboards verwenden, ist die
viewDidLoad
Methode Ihres View Controllers ein geeigneter Ort.Einige Erklärungen
Dies ist ein täuschend kompliziertes Problem. Drei wichtige Punkte, die möglicherweise nicht sofort offensichtlich sind (und deren vorherige Antworten hier alle nicht berücksichtigt werden):
Während das
XXXX XXXX XXXX XXXX
Format für Kredit- und Debitkartennummern das gebräuchlichste ist, ist es nicht das einzige. Zum Beispiel haben American Express-Karten 15-stellige Zahlen, die normalerweiseXXXX XXXXXX XXXXX
wie folgt geschrieben sind:Sogar Visa-Karten können weniger als 16 Ziffern haben, und Maestro-Karten können mehr haben:
Es gibt mehr Möglichkeiten für den Benutzer, mit einem Textfeld zu interagieren, als nur einzelne Zeichen am Ende der vorhandenen Eingabe einzugeben. Sie haben auch die Benutzer richtig zu behandeln Hinzufügen Zeichen in der Mitte der Zeichenfolge, das Löschen einzelner Zeichen, mehrere ausgewählte Zeichen Löschen und Einfügen in mehreren Zeichen. Einige einfachere / naivere Ansätze für dieses Problem werden einige dieser Interaktionen nicht richtig handhaben. Der perverseste Fall ist, dass ein Benutzer mehrere Zeichen in die Mitte der Zeichenfolge einfügt, um andere Zeichen zu ersetzen, und diese Lösung ist allgemein genug, um dies zu handhaben.
Sie müssen den Text des Textfelds nicht nur ordnungsgemäß neu formatieren, nachdem der Benutzer ihn geändert hat, sondern auch den Textcursor sinnvoll positionieren . Naive Herangehensweisen an das Problem, die dies nicht berücksichtigen, führen in einigen Fällen mit ziemlicher Sicherheit zu Dummheiten mit dem Textcursor (z. B. wenn Sie ihn an das Ende des Textfelds setzen, nachdem der Benutzer eine Ziffer in der Mitte hinzugefügt hat ).
Um das Problem Nr. 1 zu lösen, verwenden wir die teilweise Zuordnung von Kartennummernpräfixen zu Formaten, die vom Baymard Institute unter https://baymard.com/checkout-usability/credit-card-patterns kuratiert wurden . Wir können den Kartenanbieter automatisch anhand der ersten Ziffern erkennen und (in einigen Fällen) auf das Format schließen und unsere Formatierung entsprechend anpassen. Vielen Dank an cnotethegr8, der diese Idee zu dieser Antwort beigetragen hat.
Die einfachste und einfachste Möglichkeit, mit Problem Nr. 2 (und der im obigen Code verwendeten Methode) umzugehen, besteht darin, alle Leerzeichen zu entfernen und sie jedes Mal an den richtigen Positionen wieder einzufügen, wenn sich der Inhalt des Textfelds ändert, sodass wir keine Zahlen mehr benötigen herausfinden, welche Art von Textmanipulation (Einfügen, Löschen oder Ersetzen) stattfindet, und die Möglichkeiten unterschiedlich behandeln.
Um das Problem Nr. 3 zu lösen, verfolgen wir, wie sich der gewünschte Index des Cursors ändert, wenn wir nicht-stellige Zeichen entfernen und dann Leerzeichen einfügen. Aus diesem Grund führt der Code diese Manipulationen ziemlich ausführlich zeichenweise durch
NSMutableString
, anstatt dieNSString
Methoden zum Ersetzen von Zeichenfolgen zu verwenden.Schließlich lauert noch eine weitere Falle: Wenn Sie
NO
vontextField: shouldChangeCharactersInRange: replacementString
Unterbrechungen zurückkehren , wird die Schaltfläche "Ausschneiden" angezeigt, die der Benutzer erhält, wenn er Text im Textfeld auswählt. Deshalb mache ich das nicht. Die RückkehrNO
von dieser Methode führt dazu, dass 'Ausschneiden' die Zwischenablage einfach überhaupt nicht aktualisiert, und ich kenne keine Korrektur oder Problemumgehung. Infolgedessen müssen wir das Textfeld in einemUIControlEventEditingChanged
Handler neu formatieren, anstatt (offensichtlicher) in sichshouldChangeCharactersInRange:
selbst.Glücklicherweise scheinen die UIControl-Ereignishandler aufgerufen zu werden, bevor UI-Updates auf den Bildschirm übertragen werden. Daher funktioniert dieser Ansatz einwandfrei.
Es gibt auch eine ganze Reihe kleinerer Fragen zum genauen Verhalten des Textfelds, auf die offensichtlich keine richtigen Antworten vorliegen:
Wahrscheinlich ist jede Antwort auf eine dieser Fragen angemessen, aber ich liste sie auf, um zu verdeutlichen, dass es tatsächlich viele Sonderfälle gibt, über die Sie hier sorgfältig nachdenken sollten, wenn Sie besessen genug wären. Im obigen Code habe ich Antworten auf diese Fragen ausgewählt, die mir vernünftig erschienen. Wenn Sie starke Gefühle in Bezug auf einen dieser Punkte haben, die nicht mit dem Verhalten meines Codes kompatibel sind, sollte es einfach genug sein, ihn an Ihre Bedürfnisse anzupassen.
quelle
unrecognized selector sent to instance
und ein Thread-Problem, wenn ich das mache. Ideen?targetCursorPosition
am Anfang berechnen, ist kaputt. Versuchen Sie, ein Zeichen hinzuzufügen, das kein Unicode-Skalar ist, wie z. B. ein Emoji. Die Cursorposition ist ungenau.Unten finden Sie eine funktionierende Swift 4-Portierung der Antwort von Logicopolis (die wiederum eine Swift 2-Portierung einer alten Version meiner in Objective-C akzeptierten Antwort ist ), die mit dem Trick von cnotethegr8 zur Unterstützung von Amex-Karten erweitert und anschließend zur Unterstützung weiterer Karten erweitert wurde Formate. Ich schlage vor, die akzeptierte Antwort zu überprüfen, falls Sie dies noch nicht getan haben, da dies die Motivation für einen Großteil dieses Codes erklärt.
Beachten Sie, dass die minimale Reihe von Schritten erforderlich ist, um dies in Aktion zu sehen:
Main.storyboard
ein hinzufügen Textfeld .ViewController
Delegaten des Textfeldes .ViewController.swift
.IBOutlet
mit dem Textfeld .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.insertCreditCardSpaces(cardNumberWithoutSpaces, preserveCursorPosition: &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 insertCreditCardSpaces(_ string: String, preserveCursorPosition cursorPosition: inout Int) -> String { // Mapping of card prefix to pattern is taken from // https://baymard.com/checkout-usability/credit-card-patterns // UATP cards have 4-5-6 (XXXX-XXXXX-XXXXXX) format let is456 = string.hasPrefix("1") // These prefixes reliably indicate either a 4-6-5 or 4-6-4 card. We treat all these // as 4-6-5-4 to err on the side of always letting the user type more digits. let is465 = [ // Amex "34", "37", // Diners Club "300", "301", "302", "303", "304", "305", "309", "36", "38", "39" ].contains { string.hasPrefix($0) } // In all other cases, assume 4-4-4-4-3. // This won't always be correct; for instance, Maestro has 4-4-5 cards according // to https://baymard.com/checkout-usability/credit-card-patterns, but I don't // know what prefixes identify particular formats. let is4444 = !(is456 || is465) var stringWithAddedSpaces = "" let cursorPositionInSpacelessString = cursorPosition for i in 0..<string.count { let needs465Spacing = (is465 && (i == 4 || i == 10 || i == 15)) let needs456Spacing = (is456 && (i == 4 || i == 9 || i == 15)) let needs4444Spacing = (is4444 && i > 0 && (i % 4) == 0) if needs465Spacing || needs456Spacing || needs4444Spacing { stringWithAddedSpaces.append(" ") if i < cursorPositionInSpacelessString { cursorPosition += 1 } } let characterToAdd = string[string.index(string.startIndex, offsetBy:i)] stringWithAddedSpaces.append(characterToAdd) } return stringWithAddedSpaces } }
Die Anpassung an andere Situationen - beispielsweise wenn Ihr Delegierter kein Delegierter ist
ViewController
- bleibt dem Leser als Übung überlassen.quelle
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSBigMutableString substringWithRange:]: Range {20, 0} out of bounds; string length 19'
textField.undoManager?.removeAllActions()
am Ende vonreformatAsCardNumber
stoppt den Absturz. Es ist keine gute Lösung, aber es funktioniert.Sie können wahrscheinlich meinen Code optimieren oder es gibt einen einfacheren Weg, aber dieser Code sollte funktionieren:
-(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; }
quelle
Ich finde das gut:
-(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; }
quelle
if ([string length] == 0) return YES;
Swift 3- Lösung mit Fawkes- Antwort als Basis. Unterstützung für das Amex-Kartenformat hinzugefügt. Reformation hinzugefügt, wenn der Kartentyp geändert wurde.
Erstelle zuerst eine neue Klasse mit diesem 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 } }
Fügen Sie in Ihrer ViewControllerClass diese Funktion hinzu
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) }
Fügen Sie dann Ihrem textField ein Ziel hinzu
youtTextField.addTarget(self, action: #selector(self.reformatAsCardNumber(textField:)), for: UIControlEvents.editingChanged)
Registrieren Sie eine neue Variable und senden Sie den Kartentyp an diese
var selectedCardType: String? { didSet{ reformatAsCardNumber(textField: yourTextField) } }
Danke Fawkes für seinen Code!
quelle
1234 5678 9012
in meinem Textfeld habe und meinen Textcursor nach dem9
und die Rücktaste positioniere ,9
wird der gelöscht, aber mein Textcursor endet nach dem0
und nicht nach dem8
.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 ("% @ -", Präfix) chage von "-" jeder anderen Ihrer Wahl
quelle
Also wollte ich das mit weniger Code, also habe ich den Code hier verwendet und ihn ein wenig neu verwendet. Ich hatte zwei Felder auf dem Bildschirm, eines für die Nummer und eines für das Ablaufdatum, also habe ich es wiederverwendbarer gemacht.
Swift 3 alternative Antwort
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()) } }
quelle
Noch eine Version der akzeptierten Antwort in Swift 2 ...
Stellen Sie sicher, dass Sie diese in Ihrer Delegateninstanz haben:
private var previousTextFieldContent: String? private var previousSelection: UITextRange?
Stellen Sie außerdem sicher, dass Ihr Textfeld reformatAsCardNumber aufruft:
textField.addTarget(self, action: #selector(reformatAsCardNumber(_:)), forControlEvents: .EditingChanged)
Ihr Textfelddelegierter muss dies tun:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { previousTextFieldContent = textField.text; previousSelection = textField.selectedTextRange; return true }
Zuletzt schließen Sie die folgenden Methoden ein:
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 }
quelle
Hier ist eine Swift-Version für den Fall, dass dies für alle nützlich ist, die noch nach dieser Antwort suchen, aber Swift anstelle von Objective-C verwenden. Die Konzepte sind trotzdem gleich.
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 }
quelle
Um das Ziel der Formatierung des auf diese Weise in das Textfeld eingegebenen Textes zu erreichen, ist es wichtig, einige wichtige Dinge zu beachten. XXXX XXXX XXXX XXXX. Neben der Tatsache, dass die 16-stellige Kartennummer, die alle vier Ziffern getrennt wird, das am häufigsten verwendete Format ist, gibt es Karten mit 15 Ziffern (AmEx-formatiertes XXXX XXXXXX XXXXX) und andere mit 13 Ziffern oder sogar mit 19 Ziffern ( https: // en). wikipedia.org/wiki/Payment_card_number ). Eine andere wichtige Sache, die Sie berücksichtigen sollten, ist die Konfiguration des Textfelds, um nur Ziffern zuzulassen. Konfigurieren Sie den Tastaturtyp als numberPad. Dies ist ein guter Anfang, aber es ist praktisch, eine Methode zu implementieren, die die Eingabe sichert.
Ein Ausgangspunkt ist die Entscheidung, wann Sie die Nummer formatieren möchten, während der Benutzer die Nummer eingibt oder wann der Benutzer das Textfeld verlässt. Für den Fall, dass Sie formatieren möchten, wenn der Benutzer das textField verlässt, können Sie die Methode textFieldDidEndEditing (_ :) des Delegaten verwenden, um den Inhalt des textField zu übernehmen und zu formatieren.
Für den Fall, dass Sie, während der Benutzer die Nummer eingibt, die delegate-Methode textField (_: shouldChangeCharactersIn: replaceString :) nützlich ist, die aufgerufen wird, wenn sich der aktuelle Text ändert.
In beiden Fällen gibt es immer noch ein Problem. Finden Sie heraus, welches Format für die eingegebene Nummer das richtige ist. IMHO. Basierend auf allen Nummern, die ich gesehen habe, gibt es nur zwei Hauptformate: das oben beschriebene Amex-Format mit 15 Ziffern und das Formatieren Sie die Gruppenkartennummer alle vier Ziffern, wobei es egal ist, wie viele Ziffern vorhanden sind. In diesem Fall wird beispielsweise eine Karte mit 13 Ziffern wie XXXXX XXXX XXXX X formatiert und mit 19 Ziffern wie diese XXXX aussehen XXXX XXXX XXXX XXX, dies funktioniert für die häufigsten Fälle (16 Ziffern) und auch für die anderen. Sie können also herausfinden, wie Sie den AmEx-Fall mit demselben Algorithmus wie unten verwalten und mit den magischen Zahlen spielen.
Ich habe ein RegEx verwendet, um sicherzustellen, dass eine 15-stellige Karte bei anderen bestimmten Formaten ein amerikanischer Express ist
let regex = NSPredicate(format: "SELF MATCHES %@", "3[47][A-Za-z0-9*-]{13,}" ) let isAmex = regex.evaluate(with: stringToValidate)
Ich empfehle dringend, das spezifische RegEx zu verwenden, das nützlich ist, um den Emittenten zu identifizieren und herauszufinden, wie viele Ziffern akzeptiert werden sollten.
Jetzt ist mein schneller Ansatz für die Lösung mit textFieldDidEndEditing
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 }
und für shouldChangeCharactersIn: replaceString: a Swift 3.0 von Jayesh Miruliya Answer setzen Sie ein Trennzeichen zwischen die Gruppe der vier Zeichen
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 } }
quelle
Swift 3.2
Kleine Korrektur in der @ Lucas-Antwort und im Arbeitscode in Swift 3.2. Entfernen Sie auch das Leerzeichen automatisch.
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 }
quelle
Definieren Sie die folgende Methode und rufen Sie sie in UITextfield-Delegaten oder wo immer erforderlich auf
-(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; }
quelle
Swift 3-Lösung basierend auf Mark Amerys Objective-C-Lösung :
Implementieren Sie Aktionen und delegieren Sie Methoden:
textField.addTarget(self, action: #selector(reformatAsCardNumber(_:)) textField.delegate = self
TextField Delegate-Methoden und andere Methoden:
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 }
quelle
Swift 5.1, Xcode 11
Nachdem ich viele Lösungen ausprobiert hatte, hatte ich Probleme wie das Setzen der richtigen Cursorposition und das Formatieren nach Bedarf. Nachdem ich zwei Beiträge kombiniert hatte ( https://stackoverflow.com/a/38838740/10579134 , https: // stackoverflow) , fand ich schließlich eine Lösung . 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 }
Und diese Erweiterung hinzufügen
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 } }
quelle
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 } }
quelle
Hier ist eine schnelle Kopie der akzeptierten Antwort, falls jemand sie benötigt. Es ist im Grunde eine Wrapper-Klasse. Ich habe nicht zu viel Zeit damit verbracht, es zu optimieren, aber es ist einsatzbereit.
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 } }
quelle
previousTextContent
nirgendwo, also empfängt esnil
(oder, wenn Sie es zu einemString
anstelle von a machenString?
, empfängt es einige zufällige Müllbytes). Das heißt, wenn Sie 19 Zeichen überlaufen, wird das gesamte Textfeld nur ausgeblendet (oder die App stürzt sofort ab, wenn Sie Pech haben - aber bisher habe ich die Ausblendung immer gesehen).Diese Antworten sind einfach viel zu viel Code für mich. Hier ist eine Lösung 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 } } }
Fügen Sie jetzt einfach eine IBAction in Ihren View Controller ein:
@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) }
quelle
current
soll das sein?), Wird die Cursorposition nicht richtig beibehalten. zB wenn ich1234 5678
in das Textfeld habe und ein0
nach dem4
tippe, ende ich mit1234 5678 0
aber mit meinem Cursor vor dem0
ich gerade getippt habe, anstatt danach.Hier ist eine Kotlin-Antwort von Mark Amery
fun formatCardNumber(cardNumber: String): String { var trimmedCardNumber = cardNumber.replace(" ","") // UATP cards have 4-5-6 (XXXX-XXXXX-XXXXXX) format val is456 = trimmedCardNumber.startsWith("1") // These prefixes reliably indicate either a 4-6-5 or 4-6-4 card. We treat all these // as 4-6-5-4 to err on the side of always letting the user type more digits. val is465 = listOf("34", "37", "300", "301", "302", "303", "304", "305", "309", "36", "38", "39") .any { trimmedCardNumber.startsWith(it) } // In all other cases, assume 4-4-4-4. val is4444 = !(is456 || is465) trimmedCardNumber = if (is456 || is465) { trimmedCardNumber.take(cardNumberMaxLengthAmex) } else { trimmedCardNumber.take(cardNumberMaxLength) } var cardNumberWithAddedSpaces = "" trimmedCardNumber.forEachIndexed { index, c -> val needs465Spacing = is465 && (index == 4 || index == 10 || index == 15) val needs456Spacing = is456 && (index == 4 || index == 9 || index == 15) val needs4444Spacing = is4444 && index > 0 && index % 4 == 0 if (needs465Spacing || needs456Spacing || needs4444Spacing) { cardNumberWithAddedSpaces += " " } cardNumberWithAddedSpaces += c } return cardNumberWithAddedSpaces }
Fügen Sie dann einen Textänderungs-Listener zu einem Bearbeitungstext hinzu
var flag = false editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if (flag) { flag = false } else { val text = formatCardNumber(s.toString()) flag = true editText.setText(text) editText.setSelection(text.count()) } } override fun afterTextChanged(s: Editable?) {} })
quelle
Sie können meine einfache Bibliothek verwenden: DECardNumberFormatter
Beispiel:
// You can use it like default UITextField let textField = DECardNumberTextField() // Custom required setup textField.setup()
Ausgabe:
For sample card number (Visa) 4111111111111111 Format (4-4-4-4): 4111 1111 1111 1111 For sample card number (AmEx) 341212345612345 Format (4-6-5): 3412 123456 12345
quelle
schnell 5
Hallo, hier ist die schnelle 5-Version von Mark Amerys akzeptierter Antwort , die für mich funktioniert hat
Fügen Sie diese Variablen Ihrer Klasse hinzu.
@IBOutlet weak var cardNumberTextField: UITextField! private var previousTextFieldContent: String? private var previousSelection: UITextRange?
Stellen Sie außerdem sicher, dass Ihr Textfeld reformatAsCardNumber aufruft: from viewDidLoad ()
cardNumberTextField.addTarget(self, action: #selector(reformatAsCardNumber), for: .editingChanged)
Fügen Sie dies in Ihr UITextFieldDelegate ein
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == cardNumberTextField { previousTextFieldContent = textField.text; previousSelection = textField.selectedTextRange; } return true }
Fügen Sie zuletzt die folgenden Methoden in Ihren viewController ein:
@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(contentsOf: " ") if i < cursorPositionInSpacelessString { cursorPosition += 1 } } let characterToAdd = string[string.index(string.startIndex, offsetBy: i)] stringWithAddedSpaces.append(characterToAdd) } return stringWithAddedSpaces }
quelle
Bitte verwenden Sie eine einfache Form der Kreditkarte / ** Siehe Verwendungsbeispiel: ### 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 }
Ich hoffe das ist hilfreich für dich.
quelle
Ich habe die Antwort von @ilesh so geändert, dass nur die letzten 4 Ziffern angezeigt werden, unabhängig von der Länge. Auch um das Leerzeichen und "-" Zeichen zu ignorieren. Auf diese Weise wird bei einer Nummer mit dem Format 0000 - 0000 - 0000 - 0000 XXXX - XXXX - XXXX - 0000 angezeigt
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 }
quelle
UITextField
s.In Github wurde ein GIST gefunden, der genau das tut, was ich in Swift3 brauche ( https://gist.github.com/nunogoncalves/6a8b4b21f4f69e0fc050190df96a1e56 ).
Implementiert durch tun ->
if creditCardNumberTextView.text?.characters.first == "3" { let validator = Validator(cardType: .americanExpress, value: self.creditCardNumberTextView.text!).test() if validator == true { } else { } }
Funktioniert wunderbar in der App, die ich ausarbeite und die Kreditkarten verwendet.
quelle
In meinem Fall müssen wir die iban-Nummer formulieren. Ich denke, der folgende Codeblock hilft Ihnen
Überprüfen Sie zunächst, ob der vom Benutzer eingegebene Wert gültig ist
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{ if(textField == self.ibanTextField){ BOOL shouldChange = ([Help checkTextFieldForIBAN:[NSString stringWithFormat:@"%@%@",textField.text,string]]); } }
Zweitens können Sie die iban-formatierte Methode wie unten sehen. Unser iban formatierter Anfangsbuchstabe 2.
+(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; }
quelle
Hier ist die Änderung der Antwort von @sleeping_giant für schnell. Diese Lösung formatiert den Text im Format 'xxxx-xxxx-xxxx-xxxx-xxxx' und akzeptiert keine Zahlen mehr außerhalb dieses Bereichs.
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 }
quelle
Schauen Sie sich diese Lösung an. Ich fand in Autorize.net SDK Beispiel.
Machen Sie Ihren
UITextField
Tastaturtyp zuNumeric
.Es maskiert Kreditkartennummern mit 'X' und durch Hinzufügen von Leerzeichen wird das
'XXXX XXXX XXXX 1234'
Format erstellt.#define kSpace @" " #define kCreditCardLength 16 #define kCreditCardLengthPlusSpaces (kCreditCardLength + 3) #define kCreditCardObscureLength (kCreditCardLength - 4) @property (nonatomic, strong) NSString *creditCardBuf; IBOutlet UITextField *txtCardNumber;
- (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; } }
quelle
X
die Zeichenfolge(nul l)
in den Anfang des Textfelds eingefügt (mit s maskiert ) und wenn ich ein Zeichen eingebe In der Mitte der Kartennummer springt mein Cursor zum Ende.Bitte überprüfen Sie die unten stehende Lösung, sie funktioniert gut für mich.
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 }
quelle
Erstellen Sie eine neue schnelle Datei und fügen Sie sie unter den Code ein. Ändern Sie die Textfeldklasse in 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 } }
Weitere Verwendungszwecke finden Sie unter folgendem Link
Vielen Dank an den Mann, der die großartige Lösung für die Formatierung von Text in UITextField bereitgestellt hat.
http://vojtastavik.com/2015/03/29/real-time-formatting-in-uitextfield-swift-basics/
https://github.com/VojtaStavik/VSTextField
Arbeitet großartig für mich.
quelle