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:
-(void)reformatAsCardNumber:(UITextField *)textField
{
NSUInteger targetCursorPosition =
[textField offsetFromPosition:textField.beginningOfDocument
toPosition:textField.selectedTextRange.start];
NSString *cardNumberWithoutSpaces =
[self removeNonDigits:textField.text
andPreserveCursorPosition:&targetCursorPosition];
if ([cardNumberWithoutSpaces length] > 19) {
[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
{
previousTextFieldContent = textField.text;
previousSelection = textField.selectedTextRange;
return YES;
}
- (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;
}
- (NSString *)insertCreditCardSpaces:(NSString *)string
andPreserveCursorPosition:(NSUInteger *)cursorPosition
{
bool is456 = [string hasPrefix: @"1"];
bool is465 = [string hasPrefix: @"34"] ||
[string hasPrefix: @"37"] ||
[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"];
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 ein UIControlEventEditingChanged
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 normalerweise XXXX 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 die NSString
Methoden zum Ersetzen von Zeichenfolgen zu verwenden.
Schließlich lauert noch eine weitere Falle: Wenn Sie NO
von textField: 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ückkehr NO
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 einem UIControlEventEditingChanged
Handler neu formatieren, anstatt (offensichtlicher) in sich shouldChangeCharactersInRange:
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:
- Wenn der Benutzer versucht, etwas einzufügen, das dazu führen würde, dass der Inhalt des Textfelds 19 Stellen überschreitet, sollte der Anfang der eingefügten Zeichenfolge eingefügt werden (bis 19 Stellen erreicht sind) und der Rest abgeschnitten oder überhaupt nichts eingefügt werden ?
- Wenn der Benutzer versucht, ein einzelnes Leerzeichen zu löschen, indem er seinen Cursor danach positioniert und die Rücktaste drückt, sollte nichts passieren und der Cursor dort bleiben, wo er sich befindet, sollte sich der Cursor um ein Zeichen nach links bewegen (vor dem Leerzeichen platzieren) oder sollte der Ziffer links vom Leerzeichen gelöscht werden, als ob der Cursor bereits links vom Leerzeichen wäre?
- Wenn der Benutzer die vierte, achte oder zwölfte Ziffer eingibt, sollte sofort ein Leerzeichen eingefügt und der Cursor danach bewegt werden, oder sollte das Leerzeichen erst eingefügt werden, nachdem der Benutzer die fünfte, neunte oder dreizehnte Ziffer eingegeben hat?
- Wenn der Benutzer die erste Ziffer nach einem Leerzeichen löscht und dies nicht dazu führt, dass das Leerzeichen vollständig entfernt wird, sollte dies dazu führen, dass der Cursor vor oder nach dem Leerzeichen positioniert wird?
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.