gpt4 book ai didi

ios - 为信用卡输入格式化 UITextField,如 (xxxx xxxx xxxx xxxx)

转载 作者:IT老高 更新时间:2023-10-28 11:26:07 32 4
gpt4 key购买 nike

我想格式化 UITextField用于输入信用卡号,使其只允许输入数字并自动插入空格,以便数字格式如下:

XXXX XXXX XXXX XXXX

我该怎么做?

最佳答案

如果您使用 Swift,请阅读 my port of this answer for Swift 4 并改用它。

如果你在 Objective-C 中...

首先,到您的 UITextFieldDelegate ,添加这些实例变量...

NSString *previousTextFieldContent;
UITextRange *previousSelection;

...以及这些方法:
// 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;
}

其次,设置 reformatCardNumber: 以在文本字段触发 UIControlEventEditingChanged 事件时调用:
[yourTextField addTarget:yourTextFieldDelegate 
action:@selector(reformatAsCardNumber:)
forControlEvents:UIControlEventEditingChanged];

(当然,您需要在文本字段及其委托(delegate)实例化后的某个时间执行此操作。如果您使用的是 Storyboard,则 View Controller 的 viewDidLoad 方法是一个合适的位置。

一些解释

这是一个看似复杂的问题。三个可能不是很明显的重要问题(这里以前的答案都没有考虑到):
  • 虽然信用卡和借记卡号码的 XXXX XXXX XXXX XXXX 格式是最常见的格式,但它并不是唯一的格式。例如,美国运通卡有 15 位数字,通常以 XXXX XXXXXX XXXXX 格式书写,如下所示:

    An American Express card

    即使是 Visa 卡也可以有 fewer than 16 位数字,而 Maestro 卡可以有更多:

    A Russian Maestro card with 18 digits
  • 用户可以通过更多方式与文本字段进行交互,而不仅仅是在现有输入的末尾输入单个字符。您还必须正确处理用户 在字符串中间 添加字符、 删除 单个字符、删除多个选定字符以及 粘贴4027 个字符中的多个_0x1020 个字符。解决这个问题的一些更简单/更幼稚的方法将无法正确处理其中一些交互。最反常的情况是用户在字符串中间粘贴多个字符来替换其他字符,这个解决方案足以处理这个问题。
  • 在用户修改文本字段后,您不仅需要正确重新格式化文本字段的文本 - 您还需要明智地定位 文本光标 。在某些情况下,不考虑这一点的幼稚方法几乎肯定会用文本光标做一些愚蠢的事情(例如在用户在文本字段中间添加一个数字后将其放在文本字段的末尾) )。

  • 为了解决问题 #1,我们使用卡号前缀的部分映射到 Baymard Institute 在 https://baymard.com/checkout-usability/credit-card-patterns 策划的格式。我们可以从前几位数字自动检测卡提供商,并(在某些情况下)推断格式并相应地调整我们的格式。感谢 cnotethegr8 为这个答案贡献了这个想法。

    处理问题 #2 的最简单和最简单的方法(以及上面代码中使用的方法)是每次文本字段的内容发生变化时,去掉所有空格并将它们重新插入正确的位置,这样我们就不需要计算了找出正在进行的文本操作(插入、删除或替换)的类型,并以不同的方式处理可能性。

    为了解决问题#3,当我们去除非数字然后插入空格时,我们会跟踪光标的所需索引如何变化。这就是为什么代码使用 NSMutableString 逐字符执行这些操作,而不是使用 NSString 的字符串替换方法。

    最后,还有一个陷阱潜伏着:从 NO 返回 textField: shouldChangeCharactersInRange: replacementString 会破坏用户在文本字段中选择文本时获得的“剪切”按钮,这就是我不这样做的原因。从该方法返回 NO 导致“剪切”根本不更新剪贴板,我知道没有修复或解决方法。因此,我们需要在 UIControlEventEditingChanged 处理程序中重新格式化文本字段,而不是(更明显地)在 shouldChangeCharactersInRange: 本身中。

    幸运的是,UIControl 事件处理程序似乎在 UI 更新刷新到屏幕之前被调用,所以这种方法工作正常。

    还有一大堆关于文本字段应该如何表现的小问题,但没有明显的正确答案:
  • 如果用户尝试粘贴的内容会导致文本字段的内容超过 19 位,是否应该插入粘贴字符串的开头(直到达到 19 位)并裁剪其余部分,或者不应该插入任何内容全部?
  • 如果用户试图通过将光标置于其后并按退格键来删除单个空格,如果没有任何 react 并且光标保持在原处,光标应该向左移动一个字符(将其放在空格之前),或者应该空格左边的数字被删除,就好像光标已经在空格的左边一样?
  • 当用户输入第四、第八或第十二位时,是应该立即插入一个空格并将光标移到它后面,还是应该在用户输入第五、第九或第十三位后才插入空格?
  • 当用户删除空格后的第一个数字时,如果这不会导致空格被完全删除,这是否会导致他们的光标定位在空格之前或之后?

  • 可能对这些问题中的任何一个的任何答案都足够了,但我列出它们只是为了表明实际上有很多特殊情况您可能需要在这里仔细考虑,如果您足够执着的话。在上面的代码中,我选择了对我来说合理的这些问题的答案。如果您碰巧对这些与我的代码行为方式不兼容的任何一点有强烈的感觉,那么根据您的需要对其进行调整应该很容易。

    关于ios - 为信用卡输入格式化 UITextField,如 (xxxx xxxx xxxx xxxx),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12083605/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com