From c4d4b741c43c207913c906a1b71c20b94e8ed8f4 Mon Sep 17 00:00:00 2001 From: Fedya Skitsko Date: Tue, 8 Oct 2013 18:20:36 +0300 Subject: [PATCH] Credit Card type implementation --- .../Cells/RETableViewCreditCardCell.m | 117 ++++++++++-------- RETableViewManager/Items/RECreditCardItem.h | 15 ++- RETableViewManager/Items/RECreditCardItem.m | 9 +- 3 files changed, 88 insertions(+), 53 deletions(-) diff --git a/RETableViewManager/Cells/RETableViewCreditCardCell.m b/RETableViewManager/Cells/RETableViewCreditCardCell.m index 3294fdd..9d89c91 100644 --- a/RETableViewManager/Cells/RETableViewCreditCardCell.m +++ b/RETableViewManager/Cells/RETableViewCreditCardCell.m @@ -23,6 +23,7 @@ // THE SOFTWARE. // +#import "RECreditCardItem.h" #import "RETableViewCreditCardCell.h" #import "RETableViewManager.h" #import "NSString+RETableViewManagerAdditions.h" @@ -44,6 +45,14 @@ @implementation RETableViewCreditCardCell +static NSString * const creditCardTypeImage[] = { + [RECreditCardTypeUnknown] = @"RETableViewManager.bundle/Card_Stack", + [RECreditCardTypeVisa] = @"RETableViewManager.bundle/Card_Visa", + [RECreditCardTypeMasterCard] = @"RETableViewManager.bundle/Card_Mastercard", + [RECreditCardTypeDiscover] = @"RETableViewManager.bundle/Card_Discover", + [RECreditCardTypeAmex] = @"RETableViewManager.bundle/Card_Amex" +}; + static inline BOOL RECreditCardExpired(NSString *creditCardExpirationDate) { if ([creditCardExpirationDate isEqualToString:@""]) @@ -60,22 +69,25 @@ static inline BOOL RECreditCardExpired(NSString *creditCardExpirationDate) return [cardDate laterDate:firstDayOfMonthDate] == firstDayOfMonthDate; } -static inline NSString * RECreditCardType(NSString *creditCardNumber) +static inline RECreditCardType RECreditCardTypeFromNumber(NSString *creditCardNumber) { NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\D" options:NSRegularExpressionCaseInsensitive error:NULL]; NSString *strippedNumber = [regex stringByReplacingMatchesInString:creditCardNumber options:0 range:NSMakeRange(0, creditCardNumber.length) withTemplate:@""]; - - NSDictionary *types = @{@"Visa": @"^4[0-9]{12}(?:[0-9]{2})?", - @"MasterCard": @"^5[1-5][0-9]{13}", - @"Amex": @"^3[47][0-9]{12}", - @"Discover": @"^6(?:011|5[0-9]{2})[0-9]{11}"}; - - for (NSString *type in types) { - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[types objectForKey:type] options:NSRegularExpressionCaseInsensitive error:NULL]; - if ([regex numberOfMatchesInString:strippedNumber options:0 range:NSMakeRange(0, strippedNumber.length)] == 1) - return type; + + NSDictionary *types = @{ + @(RECreditCardTypeVisa) : @"^4[0-9]{12}(?:[0-9]{2})?", + @(RECreditCardTypeMasterCard) : @"^5[1-5][0-9]{13}", + @(RECreditCardTypeAmex) : @"^3[47][0-9]{12}", + @(RECreditCardTypeDiscover) : @"^6(?:011|5[0-9]{2})[0-9]{11}" + }; + + for (NSNumber *type in types) { + NSRegularExpression *rg = [NSRegularExpression regularExpressionWithPattern:[types objectForKey:type] options:NSRegularExpressionCaseInsensitive error:NULL]; + if ([rg numberOfMatchesInString:strippedNumber options:0 range:NSMakeRange(0, strippedNumber.length)] == 1) + return (RECreditCardType)type.integerValue; } - return nil; + + return RECreditCardTypeUnknown; } + (BOOL)canFocus @@ -96,13 +108,13 @@ static inline NSString * RECreditCardType(NSString *creditCardNumber) [self.contentView addSubview:self.creditCardImageViewContainer]; self.creditCardStackImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; - self.creditCardStackImageView.image = [UIImage imageNamed:@"RETableViewManager.bundle/Card_Stack"]; + self.creditCardStackImageView.image = [UIImage imageNamed:creditCardTypeImage[RECreditCardTypeUnknown]]; self.creditCardStackImageView.tag = 0; self.currentImageView = self.creditCardStackImageView; [self.creditCardImageViewContainer addSubview:self.creditCardStackImageView]; self.creditCardImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; - self.creditCardImageView.image = [UIImage imageNamed:@"RETableViewManager.bundle/Card_Visa"]; + self.creditCardImageView.image = [UIImage imageNamed:creditCardTypeImage[RECreditCardTypeVisa]]; self.creditCardImageView.tag = 1; self.creditCardBackImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; @@ -228,37 +240,37 @@ static inline NSString * RECreditCardType(NSString *creditCardNumber) - (void)textFieldDidChange:(UITextField *)textField { - if (textField.tag == 0) self.item.number = textField.text; - if (textField.tag == 1) self.item.expirationDate = textField.text; - if (textField.tag == 2) self.item.cvv = textField.text; - - NSString *issuer = RECreditCardType(self.item.number); - if (issuer) { - self.creditCardImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"RETableViewManager.bundle/Card_%@", issuer]]; - [UIView transitionFromView:self.creditCardStackImageView toView:self.creditCardImageView duration:0.4 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil]; - self.currentImageView = self.creditCardImageView; - } else { - if (self.currentImageView != self.creditCardStackImageView) { - [UIView transitionFromView:self.creditCardImageView toView:self.creditCardStackImageView duration:0.4 options:UIViewAnimationOptionTransitionFlipFromRight completion:nil]; - self.currentImageView = self.creditCardStackImageView; + if (textField.tag == 0) { + self.item.number = textField.text; + + RECreditCardType cardType = RECreditCardTypeFromNumber(self.item.number); + self.item.creditCardType = cardType; + + if (cardType != RECreditCardTypeUnknown) { + self.creditCardImageView.image = [UIImage imageNamed:creditCardTypeImage[cardType]]; + [UIView transitionFromView:self.creditCardStackImageView toView:self.creditCardImageView duration:0.4 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil]; + self.currentImageView = self.creditCardImageView; + } else { + if (self.currentImageView != self.creditCardStackImageView) { + [UIView transitionFromView:self.creditCardImageView toView:self.creditCardStackImageView duration:0.4 options:UIViewAnimationOptionTransitionFlipFromRight completion:nil]; + self.currentImageView = self.creditCardStackImageView; + } } - } - - BOOL isAmex = [issuer isEqualToString:@"Amex"]; - - if (textField.tag == 0 && textField.text.length == (isAmex ? 18 : 19) ) { - [self.expirationDateField becomeFirstResponder]; - [UIView animateWithDuration:0.1 animations:^{ - NSString *substring = [textField.text substringToIndex:textField.text.length - (isAmex ? 3 : 4)]; - CGSize size = [substring re_sizeWithFont:textField.font]; - self.creditCardField.frame = CGRectMake(-size.width, self.creditCardField.frame.origin.y, self.creditCardField.frame.size.width, self.creditCardField.frame.size.height); - self.expirationDateField.frame = CGRectMake(CGRectGetMaxX(self.creditCardField.frame), self.expirationDateField.frame.origin.y, self.expirationDateField.frame.size.width, self.expirationDateField.frame.size.height); - self.cvvField.frame = CGRectMake(CGRectGetMaxX(self.expirationDateField.frame), self.cvvField.frame.origin.y, self.cvvField.frame.size.width, self.cvvField.frame.size.height); - }]; - } - - if (textField.tag == 0 && textField.text.length == (isAmex ? 17 : 18)) { - if (textField.tag == 0) { + + BOOL isAmex = cardType == RECreditCardTypeAmex; + + if (textField.text.length == (isAmex ? 18 : 19) ) { + [self.expirationDateField becomeFirstResponder]; + [UIView animateWithDuration:0.1 animations:^{ + NSString *substring = [textField.text substringToIndex:textField.text.length - (isAmex ? 3 : 4)]; + CGSize size = [substring re_sizeWithFont:textField.font]; + self.creditCardField.frame = CGRectMake(-size.width, self.creditCardField.frame.origin.y, self.creditCardField.frame.size.width, self.creditCardField.frame.size.height); + self.expirationDateField.frame = CGRectMake(CGRectGetMaxX(self.creditCardField.frame), self.expirationDateField.frame.origin.y, self.expirationDateField.frame.size.width, self.expirationDateField.frame.size.height); + self.cvvField.frame = CGRectMake(CGRectGetMaxX(self.expirationDateField.frame), self.cvvField.frame.origin.y, self.cvvField.frame.size.width, self.cvvField.frame.size.height); + }]; + } + + if (textField.text.length == (isAmex ? 17 : 18) ) { [UIView animateWithDuration:0.1 animations:^{ self.creditCardField.frame = CGRectMake(0, self.creditCardField.frame.origin.y, self.creditCardField.frame.size.width, self.creditCardField.frame.size.height); self.expirationDateField.frame = CGRectMake(320, self.expirationDateField.frame.origin.y, self.expirationDateField.frame.size.width, self.expirationDateField.frame.size.height); @@ -267,9 +279,15 @@ static inline NSString * RECreditCardType(NSString *creditCardNumber) } } - if (textField.tag == 1 && textField.text.length == 5 && self.item.cvvRequired) { - [self.cvvField becomeFirstResponder]; + if (textField.tag == 1) { + self.item.expirationDate = textField.text; + + if(textField.text.length == 5 && self.item.cvvRequired) { + [self.cvvField becomeFirstResponder]; + } } + + if (textField.tag == 2) self.item.cvv = textField.text; } #pragma mark - @@ -284,6 +302,7 @@ static inline NSString * RECreditCardType(NSString *creditCardNumber) if (textField.tag == 2) { [UIView transitionFromView:self.currentImageView toView:self.creditCardBackImageView duration:0.4 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil]; } + return YES; } @@ -292,11 +311,7 @@ static inline NSString * RECreditCardType(NSString *creditCardNumber) [self performSelector:@selector(flipCreditCardImageViewBack:) withObject:textField afterDelay:0.1]; if (textField == self.expirationDateField) { - if (RECreditCardExpired(self.expirationDateField.text)) { - self.ribbonExpired.hidden = NO; - } else { - self.ribbonExpired.hidden = YES; - } + self.ribbonExpired.hidden = !RECreditCardExpired(self.expirationDateField.text); } return YES; } diff --git a/RETableViewManager/Items/RECreditCardItem.h b/RETableViewManager/Items/RECreditCardItem.h index 0601a8a..248eccd 100644 --- a/RETableViewManager/Items/RECreditCardItem.h +++ b/RETableViewManager/Items/RECreditCardItem.h @@ -25,6 +25,15 @@ #import "RETableViewItem.h" +typedef NS_ENUM(NSUInteger, RECreditCardType) { + RECreditCardTypeUnknown, + RECreditCardTypeVisa, + RECreditCardTypeMasterCard, + RECreditCardTypeAmex, + RECreditCardTypeDiscover +}; + + @interface RECreditCardItem : RETableViewItem // Appearance customization @@ -37,13 +46,17 @@ @property (copy, readwrite, nonatomic) NSString *expirationDate; @property (copy, readwrite, nonatomic) NSString *cvv; +@property (assign, readwrite, nonatomic) RECreditCardType creditCardType; + @property (assign, readwrite, nonatomic) BOOL cvvRequired; // Keyboard // @property (assign, readwrite, nonatomic) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault -+ (instancetype)itemWithNumber:(NSString *)number expirationDate:(NSString *)expirationDate cvv:(NSString *)cvv; ++ (instancetype)itemWithNumber:(NSString *)number expirationDate:(NSDate *)expiration cvv:(NSString *)cvv; ++ (instancetype)itemWithNumber:(NSString *)number expirationString:(NSString *)expirationDate cvv:(NSString *)cvv; + - (id)initWithNumber:(NSString *)number expirationDate:(NSString *)expirationDate cvv:(NSString *)cvv; @end diff --git a/RETableViewManager/Items/RECreditCardItem.m b/RETableViewManager/Items/RECreditCardItem.m index 719993a..d904bb2 100644 --- a/RETableViewManager/Items/RECreditCardItem.m +++ b/RETableViewManager/Items/RECreditCardItem.m @@ -27,7 +27,14 @@ @implementation RECreditCardItem -+ (instancetype)itemWithNumber:(NSString *)number expirationDate:(NSString *)expirationDate cvv:(NSString *)cvv ++ (instancetype)itemWithNumber:(NSString *)number expirationDate:(NSDate *)expiration cvv:(NSString *)cvv { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"MM/yy"; + + return [[self alloc] initWithNumber:number expirationDate:[dateFormatter stringFromDate:expiration] cvv:cvv]; +} + ++ (instancetype)itemWithNumber:(NSString *)number expirationString:(NSString *)expirationDate cvv:(NSString *)cvv { return [[self alloc] initWithNumber:number expirationDate:expirationDate cvv:cvv]; }