Reputation: 1535
I am taking credit card number from user as input. I want to allow 16 numbers and want to format space after each 4 number. For that I have done following.
-(void)cardNumberValidation:(id)sender{
UITextField *temp=sender;
if ([temp.text length]>19) {
txtCard.text= [temp.text substringToIndex:[temp.text length] - 1];
}
if ([temp.text length]==4) {
txtCard.text=[NSString stringWithFormat:@"%@ ",temp.text];
}
if ([temp.text length]==9) {
txtCard.text=[NSString stringWithFormat:@"%@ ",temp.text];
}
if ([temp.text length]==14) {
NSString *lastChar = [txtCard.text substringFromIndex:[txtCard.text length] - 1];
txtCard.text=[NSString stringWithFormat:@"%@ ",temp.text];
}
}
But now when user will remove character after entering all then if it will reach to 15 character and then user will remove one more character then this code will add space after it and will not allow to remove it further.
Can anyone give me suggestion on this or any better way to do it.
Upvotes: 4
Views: 21344
Reputation: 11
converted code for swift 3+ (Eduardo Oliveros response)
enum CardType: String {
case Unknown, Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay
static let allCards = [Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay]
var regex : String {
switch self {
case .Amex:
return "^3[47][0-9]{5,}$"
case .Visa:
return "^4[0-9]{6,}([0-9]{3})?$"
case .MasterCard:
return "^(5[1-5][0-9]{4}|677189)[0-9]{5,}$"
case .Diners:
return "^3(?:0[0-5]|[68][0-9])[0-9]{4,}$"
case .Discover:
return "^6(?:011|5[0-9]{2})[0-9]{3,}$"
case .JCB:
return "^(?:2131|1800|35[0-9]{3})[0-9]{3,}$"
case .UnionPay:
return "^(62|88)[0-9]{5,}$"
case .Hipercard:
return "^(606282|3841)[0-9]{5,}$"
case .Elo:
return "^((((636368)|(438935)|(504175)|(451416)|(636297))[0-9]{0,10})|((5067)|(4576)|(4011))[0-9]{0,12})$"
default:
return ""
}
}
}
extension UITextField {
func validateCreditCardFormat()-> (type: CardType, valid: Bool) {
// Get only numbers from the input string
var input = self.text!
let numberOnly = input.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
var type: CardType = .Unknown
var formatted = ""
var valid = false
// detect card type
for card in CardType.allCards {
if (matchesRegex(regex: card.regex, text: numberOnly)) {
type = card
break
}
}
// check validity
valid = luhnCheck(number: numberOnly)
// format
var formatted4 = ""
for character in numberOnly {
if formatted4.count == 4 {
formatted += formatted4 + " "
formatted4 = ""
}
formatted4.append(character)
}
formatted += formatted4 // the rest
// return the tuple
return (type, valid)
}
func matchesRegex(regex: String!, text: String!) -> Bool {
do {
let regex = try NSRegularExpression(pattern: regex, options: [.caseInsensitive])
let nsString = text as NSString
let match = regex.firstMatch(in: text, options: [], range: NSMakeRange(0, nsString.length))
return (match != nil)
} catch {
return false
}
}
func luhnCheck(number: String) -> Bool {
var sum = 0
let digitStrings = number.reversed().map { String($0) }
for tuple in digitStrings.enumerated() {
guard let digit = Int(tuple.element) else { return false }
let odd = tuple.0 % 2 == 1
switch (odd, digit) {
case (true, 9):
sum += 9
case (true, 0...8):
sum += (digit * 2) % 9
default:
sum += digit
}
}
return sum % 10 == 0
}
}
Upvotes: 0
Reputation: 857
In Swift
enum CardType: String {
case Unknown, Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay
static let allCards = [Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay]
var regex : String {
switch self {
case .Amex:
return "^3[47][0-9]{5,}$"
case .Visa:
return "^4[0-9]{6,}([0-9]{3})?$"
case .MasterCard:
return "^(5[1-5][0-9]{4}|677189)[0-9]{5,}$"
case .Diners:
return "^3(?:0[0-5]|[68][0-9])[0-9]{4,}$"
case .Discover:
return "^6(?:011|5[0-9]{2})[0-9]{3,}$"
case .JCB:
return "^(?:2131|1800|35[0-9]{3})[0-9]{3,}$"
case .UnionPay:
return "^(62|88)[0-9]{5,}$"
case .Hipercard:
return "^(606282|3841)[0-9]{5,}$"
case .Elo:
return "^((((636368)|(438935)|(504175)|(451416)|(636297))[0-9]{0,10})|((5067)|(4576)|(4011))[0-9]{0,12})$"
default:
return ""
}
}
}
extension UITextField{
func validateCreditCardFormat()-> (type: CardType, valid: Bool) {
// Get only numbers from the input string
var input = self.text!
let numberOnly = input.stringByReplacingOccurrencesOfString("[^0-9]", withString: "", options: .RegularExpressionSearch)
var type: CardType = .Unknown
var formatted = ""
var valid = false
// detect card type
for card in CardType.allCards {
if (matchesRegex(card.regex, text: numberOnly)) {
type = card
break
}
}
// check validity
valid = luhnCheck(numberOnly)
// format
var formatted4 = ""
for character in numberOnly.characters {
if formatted4.characters.count == 4 {
formatted += formatted4 + " "
formatted4 = ""
}
formatted4.append(character)
}
formatted += formatted4 // the rest
// return the tuple
return (type, valid)
}
func matchesRegex(regex: String!, text: String!) -> Bool {
do {
let regex = try NSRegularExpression(pattern: regex, options: [.CaseInsensitive])
let nsString = text as NSString
let match = regex.firstMatchInString(text, options: [], range: NSMakeRange(0, nsString.length))
return (match != nil)
} catch {
return false
}
}
func luhnCheck(number: String) -> Bool {
var sum = 0
let digitStrings = number.characters.reverse().map { String($0) }
for tuple in digitStrings.enumerate() {
guard let digit = Int(tuple.element) else { return false }
let odd = tuple.index % 2 == 1
switch (odd, digit) {
case (true, 9):
sum += 9
case (true, 0...8):
sum += (digit * 2) % 9
default:
sum += digit
}
}
return sum % 10 == 0
}
}
form more go to http://kalapun.com/posts/card-checking-in-swift/
Upvotes: 17
Reputation: 112857
Don't change the user's entered text, it will just cause confusion. Don't cause the user to think: WTF. The user entered the number in the way he understood, honor that as much as possible.
Just sanitize what the user has entered. Generally just remove all leading, training and interspersed space characters, possibly any non-numeric characters. Then ensure the entered text is all numeric and of the correct length.
Keep in mind that the number can have a length of 13 to 19 digits, American Express is 15 digits. See: Bank card number
Consider the code:
if ([temp.text length]>19) {
txtCard.text= [temp.text substringToIndex:[temp.text length] - 1];
}
If the user entered an extra space character between groups the last digit will be deleted. It is all to easy to come up with such a scheme will avoid all possible pitfalls.
Example: "1234 4567 9012 3456" would be truncated to "1234 4567 9012 345".
Extra, Method to verify the check digit:
+ (BOOL)isValidCheckDigitForCardNumberString:(NSString *)cardNumberString {
int checkSum = 0;
uint8_t *cardDigitArray = (uint8_t *)[cardNumberString dataUsingEncoding:NSUTF8StringEncoding].bytes;
int digitsCount = (int)cardNumberString.length;
BOOL odd = cardNumberString.length % 2;
for (int digitIndex=0; digitIndex<digitsCount; digitIndex++) {
uint8_t cardDigit = cardDigitArray[digitIndex] - '0';
if (digitIndex % 2 == odd) {
cardDigit = cardDigit * 2;
cardDigit = cardDigit / 10 + cardDigit % 10;
}
checkSum += cardDigit;
}
return (checkSum % 10 == 0);
}
BOOL checkDigitValid = [TestClass isValidCheckDigitForCardNumberString:@"371238839571772"];
NSLog(@"check digit valid: %@", checkDigitMatch ? @"yes" : @"no");
Output:
check digit valid: yes
Upvotes: 5
Reputation: 422
Solution in Swift:
let z = 4, intervalString = " "
func canInsert(atLocation y:Int) -> Bool { return ((1 + y)%(z + 1) == 0) ? true : false }
func canRemove(atLocation y:Int) -> Bool { return (y != 0) ? (y%(z + 1) == 0) : false }
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let nsText = textField.text! as NSString
if range.location == 19 { return false }
if range.length == 0 && canInsert(atLocation: range.location) {
textField.text! = textField.text! + intervalString + string
return false
}
if range.length == 1 && canRemove(atLocation: range.location) {
textField.text! = nsText.stringByReplacingCharactersInRange(NSMakeRange(range.location-1, 2), withString: "")
return false
}
return true
}
Upvotes: 1
Reputation: 392
You can do this by using following:
class PaymentViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var textFieldCardNumber: UITextField!
//for keeping track of cursor in text field for setting limit by Chetan
var cardNumberCursorPreviousPosition = 0
//MARK: - LifeCycle Methods
override func viewDidLoad() {
super.viewDidLoad()
self.textFieldCardNumber.delegate = self
//for applying did change event on text fields
self.textFieldCardNumber.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textFieldDidChange(textField: UITextField) {
//logic for adding hyphen after each 4 digits
if textField == self.textFieldCardNumber {
if count(self.textFieldCardNumber.text) <= 7 {
var cardType: String = cardValidator.checkCardType(self.textFieldCardNumber.text)
println("Card Type : \(cardType)")
}
//for applying hyphen
if (count(textFieldCardNumber.text) == 4 && cardNumberCursorPreviousPosition == 3) || (count(textFieldCardNumber.text) == 9 && cardNumberCursorPreviousPosition == 8) ||
(count(textFieldCardNumber.text) == 14 && cardNumberCursorPreviousPosition == 13) {
textFieldCardNumber.text = "\(textFieldCardNumber.text)-"
}
//for removing hyphen and its preceding character/number
if (count(textFieldCardNumber.text) == 4 && cardNumberCursorPreviousPosition == 5) ||
(count(textFieldCardNumber.text) == 9 && cardNumberCursorPreviousPosition == 10) ||
(count(textFieldCardNumber.text) == 14 && cardNumberCursorPreviousPosition == 15) {
textFieldCardNumber.text = textFieldCardNumber.text.substringToIndex(advance(textFieldCardNumber.text.endIndex, -1))
}
cardNumberCursorPreviousPosition = count(textFieldCardNumber.text)
}
}
In the above code I have given hyphen. You can replace it with space.
Upvotes: 0
Reputation: 3334
You should use payment kit a very powerful and easy to use API for such tasks.
Upvotes: -5
Reputation: 41226
If you want to maintain your current approach, I'd suggest stripping all the spaces out and then reinserting them at the right places, something like:
-(void)cardNumberValidation:(id)sender
{
NSString* text = [sender text];
// Strip out all spaces
text = [text stringByReplacingOccurrencesOfString:@" " withString:@""];
// Truncate to 16 characters
if(text.length)
text = [text substringToIndex:16];
// Insert spaces
if(text.length > 12)
text = [text stringByReplacingCharactersInRange:NSMakeRange(12, 0) withString:@" "];
if(text.length > 8)
text = [text stringByReplacingCharactersInRange:NSMakeRange(8, 0) withString:@" "];
if(text.length > 4)
text = [text stringByReplacingCharactersInRange:NSMakeRange(4, 0) withString:@" "];
[sender setText:text];
}
That said, the idea of changing the users text on them can be confusing to the user, and this approach is very dependent on only accepting VISA and/or MasterCard as other card issuers use different formats.
Upvotes: 1
Reputation: 89
Here is my quick solution:
#define kLENGTH 4
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (string.length > 0) {
NSUInteger length = textField.text.length;
int cntr = (int)((length - (length/kLENGTH)) / kLENGTH);
if (!(((length + 1) % kLENGTH) - cntr)) {
NSString *str = [textField.text stringByAppendingString:[NSString stringWithFormat:@"%@ ", string]];
textField.text = str;
return NO;
}
} else {
if ([textField.text hasSuffix:@" "]) {
textField.text = [textField.text substringToIndex:textField.text.length - 2];
return NO;
}
}
return YES;
}
Upvotes: 1
Reputation: 3444
i am using this one in one of my app for credit card like format
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init] ;
if([string length]==0)
{
[formatter setGroupingSeparator:@"-"];
[formatter setGroupingSize:4];
[formatter setUsesGroupingSeparator:YES];
[formatter setSecondaryGroupingSize:2];
NSString *num = textField.text ;
num= [num stringByReplacingOccurrencesOfString:@"-" withString:@""];
NSString *str = [formatter stringFromNumber:[NSNumber numberWithDouble:[num doubleValue]]];
[formatter release];
textField.text=str;
NSLog(@"%@",str);
return YES;
}
else {
[formatter setGroupingSeparator:@"-"];
[formatter setGroupingSize:2];
[formatter setUsesGroupingSeparator:YES];
[formatter setSecondaryGroupingSize:2];
NSString *num = textField.text ;
if(![num isEqualToString:@""])
{
num= [num stringByReplacingOccurrencesOfString:@"-" withString:@""];
NSString *str = [formatter stringFromNumber:[NSNumber numberWithDouble:[num doubleValue]]];
[formatter release];
textField.text=str;
}
//NSLog(@"%@",str);
return YES;
}
//[formatter setLenient:YES];
}
Upvotes: 2