格式化UITextField以进行信用卡输入,例如(xxxx xxxx xxxx xxxx)


72

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

XXXX XXXX XXXX XXXX

我怎样才能做到这一点?


1
如果可以使用开源库,我强烈建议您查看PaymentKit(github.com/stripe/PaymentKit)。它们具有可以使用的格式化程序,并且可以用于所有类型的卡(并且具有用于luhn检查和其他功能的验证器)。
Mike Welsh

@MikeWelsh很有意思,也许-就我所知-一种比我的答案更好的方法,但是我没有时间或倾向去研究它(特别是因为我不再拥有Mac,并且没有做任何事情)一年多的iOS开发)。如果您有使用该库的经验,那么写一个答案来展示如何使用它的简单示例,可能对未来的读者而言,不仅仅是评论而已。
Mark Amery

如果您正在寻找动态方法,此答案可能会有所帮助。stackoverflow.com/a/38560759/3947151
Tesan3089,2016年

1
这个问题继续吸引着人们的答案,他们认为通过提供比我(接受)的答案更短,更简单的答案,他们会有所帮助。这些答案的确更短和更简单的-作为一个结果,而不是他们的一个作品之一!(是的,我已经亲自测试了每个。)人们,这是一个看似困难的问题!如果您想提供一个更好的答案,请至少阅读我答案的“说明”部分,以及我留下的许多很多评论来解释破坏他人实现的方式,并确保您没有失败以同样的方式。
Mark Amery

这个答案将帮助,如果你想有一个紧凑的解决方案,并在迅速语言 stackoverflow.com/questions/37190620/...
蒂娜·纳特保罗

Answers:


144

如果您使用的是Swift,请阅读我的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];

(当然,您需要在实例化文本字段及其委托之后的某个时候执行此操作。如果使用情节提要,viewDidLoad则适合使用视图控制器的方法。

一些解释

这是一个看似复杂的问题。可能不会立即显而易见的三个重要问题(以及此处先前的答案都没有考虑在内):

  1. XXXX XXXX XXXX XXXX信用卡和借记卡卡号格式是最常见的格式,但不是唯一的格式。例如,美国运通卡有15位数字,通常以以下XXXX XXXXXX XXXXX格式书写:

    美国运通卡

    甚至Visa卡的位数也可以少于16位,Maestro卡的位数可以更多:

    带有18位数字的俄罗斯Maestro卡

  2. 除了与现有输入末尾输入单个字符外,用户还有更多与文本字段进行交互的方式。您还必须正确处理用户在字符串中间添加字符删除单个字符,删除多个选定字符以及粘贴多个字符的问题。解决此问题的一些更简单/更幼稚的方法将无法正确处理其中的某些交互。最不正常的情况是用户在字符串的中间粘贴多个字符以替换其他字符,这种解决方案通常足以解决该问题。

  3. 用户修改后,您不仅需要重新格式化文本字段的文本,还需要合理地定位文本光标。不考虑这一问题的幼稚方法几乎肯定会在某些情况下最终使文本光标做一些愚蠢的事情(例如,在用户在中间添加数字后将其放在文本字段的末尾) )。

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

解决问题#2(以及上面的代码中使用的方法)的最简单,最简单的方法是,每次文本字段的内容更改时,都将所有空格删除并重新插入正确的位置,从而避免了我们需要计算找出正在进行哪种文本操作(插入,删除或替换),并以不同的方式处理各种可能性。

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

最后,还有另外一个陷阱:NOtextField: shouldChangeCharactersInRange: replacementString中断返回时,用户在文本字段中选择文本时会得到“剪切”按钮,这就是为什么我不这样做。NO从该方法返回将导致“剪切”根本不更新剪贴板,而且我知道没有修复方法或解决方法。结果,我们需要对UIControlEventEditingChanged处理程序中的文本字段进行重新格式化,而不是对它shouldChangeCharactersInRange:本身进行重新格式化。

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

关于文本字段应如何表现,还有很多次要问题,这些问题没有明显的正确答案:

  • 如果用户尝试粘贴会导致文本字段的内容超过19位的内容,则应插入粘贴的字符串的开头(直到19位数字为止),其余部分将被裁剪,或者完全不插入任何内容?
  • 如果用户尝试通过以下方式删除单个空格:将光标定位在空格之后,然后按Backspace键,则什么也不会发生,并且光标仍停留在原处;光标应该向左移动一个字符(将其放在空格之前),还是删除空格左侧的数字,就好像光标已经位于空格左侧了吗?
  • 当用户输入第四,第八或第十二位数字时,应立即插入空格并在其后移动光标,还是仅应在用户键入第五,第九或第十三位数字后插入空格?
  • 当用户删除空格后的第一位数字时,如果这不能完全删除空格,这是否会导致他们的光标位于空格之前或之后?

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


1
unrecognized selector sent to instance这样做时遇到了线程问题。有想法吗?
Jordan Feldstein 2014年

我可以使用以下差异修复它:cl.ly/image/45182G0Z3r1O将引用存储在控制器上似乎阻止了它被垃圾收集,这首先就是产生错误的原因。希望有帮助!cc @MarkAmery,以防他想查看和更新​​其解决方案。
乔丹·费尔德斯坦

@JordanFeldsteint很高兴您解决了您的问题。我认为,您正在描述的内容(必须保留对对象的引用以避免它们被垃圾回收)是Objective-C中带有ARC的一个标准问题,并且超出了此答案的范围。顺便说一句,做这种记账的麻烦是许多人(包括我,包括演示中的Apple开发人员)喜欢简单地将其视图控制器用作所有内容的委托而不是创建其他对象以用作对象的原因之一。代表。即使在复杂的视图中,在Xcode中使用pragma标记也可以轻松实现此目的。
Mark Amery

@MarkAmery我已经以小型图书馆的形式实现了您的想法,以管理文本字段格式github.com/chebur/CHRTextFieldFormatter
chebur 2015年

@MarkAmery他们targetCursorPosition在开始时计算自己的方式已损坏。尝试添加不是Unicode标量的字符,例如表情符号。光标位置将不正确。
HHK

31

以下是Logicopolis答案的有效的Swift 4端口(依次是我在Objective-C中接受的答案的旧版本的Swift 2端口),使用cnotethegr8 支持美国运通卡的技巧进行了增强,然后进一步增强了以支持更多卡格式。我建议您仔细查看已接受的答案,因为它有助于解释许多此代码背后的动机。

请注意,执行此操作所需的最少步骤是:

  1. 在Swift中创建一个新的Single View App
  2. 在上Main.storyboard,添加一个文本字段
  3. 使Text FieldViewController的代表。
  4. 将以下代码粘贴到中ViewController.swift
  5. 将连接IBOutlet文本字段
  6. 运行您的应用,然后在文本字段中输入

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

ViewController让读者适应于其他情况(例如您的代理人不是)的练习。


仅当我使用DispatchQueue.main.async将它分派到主线程上时,textField.selectedTextRange = textField.textRange(从:targetPosition,至:targetPosition)才能正常工作。我想此时textview处于textField.selectedTextRange的状态被忽略。重新调度可以解决该问题。
scrrr

我认为这可能会崩溃。输入4111111111111111111(应类似于4111 1111 1111 1111 111),在最后三位数字上退格,摇动以撤消并选择撤消。*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSBigMutableString substringWithRange:]: Range {20, 0} out of bounds; string length 19'
乔什·帕拉多里德

@JoshParadroid Ooo,是的。:(我可以立即相信您-我知道在2013年在Objective C中首次实现此功能时,我并未考虑过“撤消”。哎呀,我不确定iOS 6中是否甚至“撤消”功能。我我有机会时会看一下,看看是否有什么我可以解决的办法,但是我不知道是否有办法在正确支持“撤消”和也满足了我在接受的答案中概述的其他约束条件;也许可以抑制按钮,使用户根本无法“撤消”?
Mark Amery

@MarkAmery对于我当前正在使用它的情况,抑制它就足够了,但是我目前没有那么运气。感谢您对一个老问题的快速答复。
乔什·帕拉多里德

1
@MarkAmerytextField.undoManager?.removeAllActions()结尾的reformatAsCardNumber将停止崩溃的发生。这不是一个很好的解决办法,但可以。
乔什·帕拉多里德

26

您可能可以优化我的代码,或者可能有一种更简单的方法,但是此代码应该可以工作:

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

1
此解决方案至少存在一个重大缺陷:如果我键入例如“ 1234”,然后将文本光标移动到“ 1”之后,然后键入或删除一个字符,突然我的文本光标跳到末尾再次输入文本字段。
Mark Amery 2013年

这会破坏正在存储的文本吗?还是只影响显示的文字?
詹姆斯·坎贝尔,

12

我认为这个很好:

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

1
这个答案也坏了。如果我回去,并删除字符键入号码后,空间的定位得到打破。
马克·阿梅里奥

1
这会破坏吗? if ([string length] == 0) return YES;
卢卡斯2014年

是的-还是有负载这里更坏。一方面,只要我将文本光标一直移动到框的左侧,我就可以输入任意长的数字!
Mark Amery

11

使用Fawkes回答的Swift 3解决方案是基本的。添加了美国运通卡格式支持。卡类型更改时添加了重新设置。

首先使用以下代码创建新类:

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
    }

}

在您的ViewControllerClass中添加此功能

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

然后将目标添加到您的textField

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

注册新变量并向其发送卡类型

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

感谢福克斯的代码!


1
withPreviousTextContent没有正确的值。
Aliaksandr Bialiauski '17

-1; 这不能正确处理删除空格后的数字。如果我1234 5678 9012在文本字段中,并且将文本光标放在的后面9并按退格键,9则会被删除,但是我的文本光标在0而不是之后结束8
Mark Amery

8
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)“-”的任何其他选择


-1; 像这里的许多其他答案一样,如果您移动文本光标,这的行为将非常可怕。无论我在何处键入数字,输入的每个数字都将跳到文本字段的右侧。
Mark Amery

它对我有用,但将其转换为最新的快速代码。
Shanu Singh,

8

所以我想用更少的代码来做到这一点,所以我在这里使用了代码并重新调整了它的用途。我在屏幕上有两个字段,一个用于数字,另一个用于到期日期,因此我使它更具可重用性。

Swift 3替代答案

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

-1。每个人都希望用更少的代码来做到这一点,但是到目前为止,没有人成功。这个答案(和其他大多数答案一样)以我在警告中已经警告过的方式被破坏:如果将文本光标移动到文本字段末尾以外的其他地方并键入数字,则文本光标会跳至文本字段的末尾,这不应该发生。
Mark Amery

5

Swift 2中已接受答案的另一个版本...

确保您的委托实例中包含以下内容:

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

还要确保您的文本字段调用了formatatAsCardNumber:

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

您的文本字段委托将需要执行以下操作:

func textField(textField: UITextField, shouldChangeCharactersInRange 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.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
}

2
太好了-这是我的答案唯一有效的Swift转换;实际上,除了我的问题外,这是唯一一个在这个问题引起的令人震惊的27个答案(主要是垃圾)中起作用的唯一答案。我对此进行了编辑,以注意它适用于Swift 2,并且也将其用作我自己的Swift 4端口的基础。只想说谢谢,让您知道!
Mark Amery

4

这是一个Swift版本,以防任何仍在寻找答案但使用Swift而不是Objective-C的人使用。无论如何,概念仍然相同。

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
}

好的代码。为我工作,但有一个问题。它将在字符串的开头放置空格。例如,如果我要编写4242424242424242,则该字符串的输出为“ 4242 4242 4242 4242”。
伯茹

-1; 除了@Birju开头提到的空格之外,如果我将文本光标移动到字符串中的较早位置,这也会被破坏;如果我在此处输入文字,不仅会打破4个数字块之间的间距,还会使我超出字符数限制。
Mark Amery

4

为了实现格式化的目的,以这种方式在文本字段中输入的文本XXXX XXXX XXXX XXXX对于记住一些重要的事情很重要。除最常用的格式是每四位分隔的16位卡号外,还有15位(AmEx格式为XXXX XXXXXX XXXXX)的卡,还有其他13位或什至19位的卡(https:// en。 wikipedia.org/wiki/Payment_card_number )。您应该考虑的其他重要事项是将textField配置为仅允许数字,将键盘类型配置为numberPad是一个不错的开始,但是实现保护输入安全的方法很方便。

起点是确定何时格式化数字,用户输入数字时或何时离开文本字段。如果要在用户离开textField时进行格式化,则可以方便地使用textFieldDidEndEditing(_ :)委托的方法获取textField的内容并对其进行格式化。

在用户输入数字的情况下,textField(_:shouldChangeCharactersIn:replacementString :)委托方法很有用,只要当前文本更改,该方法就会被调用。

在这两种情况下,仍然存在一个问题,找出输入号码的正确格式,恕我直言,根据我看到的所有数字,只有两种主要格式:上述15位数字的Amex格式和格式化哪个卡号每四位数而不关心位数有多少,在这种情况下,就像通用规则一样,例如,具有13位数字的卡将被格式化为XXXXX XXXX XXXX X,而具有19位数字的卡将看起来像此XXXX XXXX XXXX XXXX XXX,这适用于最常见的情况(16位数字),也适用于其他情况。因此,您可以找出如何使用下面的魔术数字管理相同的算法来管理AmEx案件。

对于其他特殊格式,我使用RegEx来确保15位数字卡是美国运通卡

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

我强烈建议使用特定的RegEx,这对于识别发卡行和确定应接受的数字位数很有用。

现在,我使用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
}

对于shouldChangeCharactersIn:replacementString:Swift 3.0,来自Jayesh Miruliya Answer,在四个字符组之间放置分隔符

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

这段代码看起来很吓人。您可以使用“ while”而不是3条if语句,并避免使用许多不必要的变量
user3206558 2015年

-1; 正如您所注意到的,这里的大多数代码只是从另一个用户的答案中复制并粘贴的(而且,正如我在该答案上所指出的那样,它是行不通的),其余代码则无法回答问题。
Mark Amery

4

斯威夫特3.2

@Lucas答案中的一些更正以及Swift 3.2中的工作代码。还会自动删除空格字符。

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
}

1
-1; 这完全被打破了。如果将光标移动到文本字段末尾以外的任何地方并键入内容,则可能会破坏格式并违反长度限制。另外,如果我将光标移到文本字段末尾的某个位置并删除字符,则会发生真正的怪异事件。整个字符都被切掉了。
Mark Amery

3

定义以下方法并在UITextfield委托中或任何需要的地方调用

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

我不清楚您打算如何使用此方法,并且它对处理文本光标定位没有任何作用。-1。
Mark Amery's

2

基于Mark Amery的Objective-C解决方案的Swift 3解决方案

  1. 实现动作和委托方法:

    textField.addTarget(self, action: #selector(reformatAsCardNumber(_:))
    textField.delegate = self
    
  2. TextField委托方法和其他方法:

    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
    }
    

我已经将此答案的归因进行了编辑,可以看到它是基于我的(它具有相同的变量和方法名称)。对于版权侵权和窃,我相当宽容,我想也许您认为归因在这里并不重要,因为我的回答实际上是在同一页上,但是没有明确地将代码从一种语言编写为另一种语言表示这就是事实,或者链接到原始来源(在这种情况下,给予归属是微不足道的)对我来说仍然是错误的。因此,我为此保留了-1。
Mark Amery's

2

Swift 5.1,Xcode 11

尝试了许多解决方案后,我面对如设置正确的光标位置,在格式化按需要的问题,我终于找到了一个解决方案结合2个职位后(https://stackoverflow.com/a/38838740/10579134https://开头计算器。 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
}

并添加此扩展名

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

2

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

1

如果有人需要,这里是快速答复的副本。基本上,这是一个包装器类。我没有花太多时间优化它,但是已经可以使用了。

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

1
-1; 您没有previousTextContent在任何地方进行设置,因此它会接收nil(或者,如果将其设为aString而不是A ,则会String?接收到一些随机垃圾字节)。这意味着,如果您溢出19个字符,则整个文本字段将变为空白(或者,如果您不走运,则应用程序可能会完全崩溃-但到目前为止,我一直都看到空白)。
Mark Amery

2
@MarkAmery我很佩服您对本文中每个解决方案的辛勤工作和深入的分析:)如上所述,这只是一个快速的解决方案,可能无法解决某些极端情况,这也鼓励人们不仅将粘贴的解决方案复制粘贴在堆栈上,而且也能理解并提供更好的答案ˆ。ˆ祝您有个美好的一天(;
Fawkes,2016年

1

这些答案对我来说都是太多的代码。这是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
        }
    }
}

现在,只需在您的视图控制器中放置一个IBAction即可:

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

-1; 撇开这,这会给读者留下很多工作(并且不能按现状编译;这current意味着什么?),这不能正确保留光标位置。例如,如果我1234 5678在文本字段,然后键入04,我结了1234 5678 0,但我的光标之前0我刚刚输入而不是之后。
Mark Amery

1

这是基于Mark Amery的Kotlin答案

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
}

然后在编辑文本上添加文本更改的侦听器

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

1

您可以使用我的简单库:DECardNumberFormatter

例:

// You can use it like default UITextField
let textField = DECardNumberTextField()
// Custom required setup
textField.setup()

输出:

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

1

迅捷5

嗨,这是Mark Amery接受的答案的迅速5版本对我

将这些变量添加到您的类中。

@IBOutlet weak var cardNumberTextField: UITextField!
private var previousTextFieldContent: String?
private var previousSelection: UITextRange?

并确保您的文本字段从viewDidLoad()调用ReformatAsCardNumber:

cardNumberTextField.addTarget(self, action: #selector(reformatAsCardNumber), for: .editingChanged)

将此添加到您的UITextFieldDelegate中

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

最后,在您的viewController中包含以下方法:

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

0

请使用信用卡的简单形式/ **参见示例用法:### 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
}

希望对您有帮助。


-1为拼写错误和注释代码;他们并没有完全激发人们相信这会做任何明智的事情。
Mark Amery's

0

我修改了@ilesh答案,因此无论长度是多少,它仅显示最后4位数字。还要忽略空格和“-”字符。这样,如果我们有一个格式为0000-0000-0000-0000的数字,它将显示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
}

-1因缺少使用说明和不完整的英语。就像ilesh的答案一样,这个答案甚至都没有提到UITextFields。
Mark Amery

0

在Github中找到了一个GIST,它完全符合我在Swift3中所需要的(https://gist.github.com/nunogoncalves/6a8b4b21f4f69e0fc050190df96a1e56

通过执行实现->

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

      if validator == true {

       } else {

       }
   }

在我正在开发的使用信用卡的APP中,它表现出色。


-1; Gist用于验证卡号,而不用于格式化卡号。这不是对所提问题的答案。
Mark Amery

0

就我而言,我们必须格式化iban号。我认为,以下代码块可以帮助您

首先,检查用户输入的值是否有效

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{

    if(textField == self.ibanTextField){

           BOOL shouldChange =  ([Help checkTextFieldForIBAN:[NSString stringWithFormat:@"%@%@",textField.text,string]]);
 }
}

其次,您可以看到iban格式的方法,如下所示。我们的iban格式的开头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;
}

0

这是对@sleeping_giant答案的修改,以便迅速进行。此解决方案以'xxxx-xxxx-xxxx-xxxx-xxxx'格式设置文本格式,并停止接受超出该范围的任何数字。

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
}

-1; 像这里的许多答案一样,如果我将文本光标移到文本字段的左侧并在其中插入一些数字,则此操作将完全中断。
Mark Amery

0

签出此解决方案。我在Autorize.net SDK中找到了示例中。

使您的UITextField键盘类型成为Numeric

它将用'X'掩盖信用卡号,并通过添加空格将其'XXXX XXXX XXXX 1234'格式化。

在标题.h文件中

    #define kSpace @" "
    #define kCreditCardLength 16
    #define kCreditCardLengthPlusSpaces (kCreditCardLength + 3)
    #define kCreditCardObscureLength (kCreditCardLength - 4)

    @property (nonatomic, strong) NSString *creditCardBuf;
    IBOutlet UITextField *txtCardNumber;

在.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;
    }
}

-1; 这是无可救药的。撇开使它成为语法错误的魔术引号(因为至少我可以轻松解决这些错误),在我第一次键入时,它将X字符串插入(用s遮盖)(nul l)到文本字段的开头,并且如果我键入字符在卡号的中间,它将光标移到末尾。
Mark Amery

0

请检查波纹管解决方案,它对我来说很好用-

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
    }

-1; 撇开帖子的格式和英文不正确,以及此处有关到期日期的大量不相关代码的事实,这是行不通的。如果将文本光标移动到文本字段中最右边的位置以外的地方并键入,格式将被破坏。
Mark Amery

0

创建新的swift文件并粘贴以下代码,将文本字段类更改为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
    }
}

在下面的链接中可以找到更多的用途

感谢为UITextField中的文本格式提供出色解决方案的人。

http://vojtastavik.com/2015/03/29/real-time-formatting-in-uitextfield-swift-basics/

https://github.com/VojtaStavik/VSTextField

对我来说很棒。


1
-1; 除了此处的代码是从库中复制并粘贴而没有明确标记这样的事实之外,它也无法回答问题(甚至实际上还提到了信用卡),并且您从中复制代码的库中存在重大错误,答案没有。
Mark Amery

@MarkAmery您有权投票。但是最近我使用了这个库,发现对您有所帮助。可能其他人会发现此帮助。
Haripal Wagh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.