Merge pull request #101 from XBeg9/creditcard-type

Credit Card type implementation
This commit is contained in:
Roman Efimov
2013-10-09 08:59:15 -07:00
3 changed files with 88 additions and 53 deletions

View File

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

View File

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

View File

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