// 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;
}
-(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;
}
这里有一个 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
}
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)
}
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
}
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
}
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
}
let regex = NSPredicate(format: "SELF MATCHES %@", "3[47][A-Za-z0-9*-]{13,}" )
let isAmex = regex.evaluate(with: stringToValidate)
我强烈建议使用特定的正则表达式,这对于识别发行者和计算应该接受多少位数字是有用的。
现在,我使用 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
}
以及 should dChangePersontersIn: replace ementString: a Swift 3.0 From 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
}
}
下面是 Logicopolis 的答案的一个工作的 Swift 4端口(反过来是 Objective-C 中我的 接受的答案旧版本的 Swift 2端口) ,用 < a href = “ https://stackoverflow. com/users/278629/cnote thegr8”> cnote thegr8进行了增强
的技巧,支持美国运通卡,然后进一步增强,以支持更多的卡格式。如果您还没有接受这个答案,我建议您仔细阅读它,因为它有助于解释许多代码背后的动机。
请注意,要看到这种情况的实际发生,需要执行的最小步骤是:
在 Swift 中创建一个新的 单视图应用程序。
在 Main.storyboard上添加 文本字段。
使 ViewController成为 文本字段的委托。
将下面的代码粘贴到 ViewController.swift中。
将 IBOutlet连接到 文本字段。
运行你的应用程序,输入 文本字段。
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
}
}
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
}
}
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?) {}
})
// 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