Skip to content

Instantly share code, notes, and snippets.

@jcoleman
Last active January 7, 2020 15:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcoleman/6831014 to your computer and use it in GitHub Desktop.
Save jcoleman/6831014 to your computer and use it in GitHub Desktop.
- (void) setExpirationTextField:(UITextField*)expirationTextField {
_expirationTextField = expirationTextField;
JCTextFieldSetKeyboardToDigits(expirationTextField);
}
- (void) setCardNumberTextField:(UITextField*)cardNumberTextField {
_cardNumberTextField = cardNumberTextField;
JCTextFieldSetKeyboardToDigits(cardNumberTextField);
}
- (BOOL) textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)replacementString {
if (textField == self.expirationTextField) {\
NSArray* digitSets = JCTextFieldHandleCharactersChangeForDigitSetsField(textField, range, replacementString, @[@(2), @(4)], @" / ");
if (!inTextFieldUpdateRecursion) {
inTextFieldUpdateRecursion = YES;
self.creditCard.expirationMonth = digitSets[0];
self.creditCard.expirationYear = digitSets[1];
inTextFieldUpdateRecursion = NO;
}
return NO;
} else if (textField == self.cardNumberTextField) {
__block NSArray* digitSetsLengths = nil;
[self.cardNumberSectionDigitCountsByCardNumberPrefix enumerateKeysAndObjectsUsingBlock:^(NSString* prefix, NSArray* _digitSetLengths, BOOL* stop) {
if ( (range.location == 0 && [[textField.text stringByReplacingCharactersInRange:range withString:replacementString] hasPrefix:prefix])
|| (range.location != 0 && [textField.text hasPrefix:prefix])) {
digitSetsLengths = _digitSetLengths;
*stop = YES;
}
}];
if (!digitSetsLengths) {
digitSetsLengths = @[@(4), @(4), @(4), @(4)];
}
NSArray* digitSets = JCTextFieldHandleCharactersChangeForDigitSetsField(textField, range, replacementString, digitSetsLengths, @" ");
if (!inTextFieldUpdateRecursion) {
inTextFieldUpdateRecursion = YES;
self.creditCard.cardNumber = [[digitSets componentsJoinedByString:@""] stringByReplacingOccurrencesOfString:@"_" withString:@""];
inTextFieldUpdateRecursion = NO;
}
return NO;
} else {
return YES;
}
}
#import <Foundation/Foundation.h>
#ifndef GlobalDriver2Passenger_JCTextFieldFunctions_h
#define GlobalDriver2Passenger_JCTextFieldFunctions_h
NSArray* JCTextFieldHandleCharactersChangeForDigitSetsField(UITextField* textField, NSRange range, NSString* replacementString, NSArray* digitSetLengths, NSString* separator);
void JCTextFieldSetKeyboardToDigits(UITextField* textField);
#endif
#import "JCTextFieldFunctions.h"
@implementation NSString (OFStringHelpers)
- (NSString*) stringByRemovingNonDigitsAndUnderscores {
NSMutableString* string = [[NSMutableString alloc] initWithCapacity:self.length];
size_t bufferSize = 32;
NSRange range = NSMakeRange(0, bufferSize);
NSUInteger end = [self length];
while (range.location < end) {
unichar buffer[bufferSize];
unichar substringBuffer[bufferSize];
NSUInteger substringLength = 0;
if (range.location + range.length > end) {
range.length = end - range.location;
}
[self getCharacters:buffer range:range];
range.location += bufferSize;
for (NSUInteger i = 0 ; i < range.length; ++i) {
unichar c = buffer[i];
if ((c >= '0' && c <= '9') || c == '_') {
substringBuffer[substringLength] = c;
++substringLength;
}
}
[string appendString:[NSString stringWithCharacters:substringBuffer length:substringLength]];
}
return string;
}
@end
NSArray* JCTextFieldHandleCharactersChangeForDigitSetsField(UITextField* textField, NSRange range, NSString* replacementString, NSArray* digitSetLengths, NSString* separator) {
NSString* existingText = (textField.text ? textField.text : @"");
NSString* textWithReplacements = [existingText stringByReplacingCharactersInRange:range withString:replacementString];
NSString* filteredExistingText = [existingText stringByRemovingNonDigitsAndUnderscores];
NSString* filterdTextWithReplacements = [textWithReplacements stringByRemovingNonDigitsAndUnderscores];
NSString* filteredReplacementString = [replacementString stringByRemovingNonDigitsAndUnderscores];
NSRange filteredRange = NSMakeRange([[existingText substringToIndex:range.location] stringByRemovingNonDigitsAndUnderscores].length,
[[existingText substringWithRange:range] stringByRemovingNonDigitsAndUnderscores].length);
if (filteredRange.length < filteredReplacementString.length) {
// Net addition of characters; overwrite any available underscores.
NSUInteger filteredRangeEnd = filteredRange.location + filteredRange.length;
NSUInteger availableUnderscores = 0;
if (filteredExistingText.length > filteredRangeEnd) {
availableUnderscores = [filteredExistingText rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"_"]
options:NSAnchoredSearch
range:NSMakeRange(filteredRangeEnd, filteredExistingText.length - filteredRangeEnd)].length;
}
if (availableUnderscores) {
NSUInteger replacedUnderscores = MIN(availableUnderscores, filteredReplacementString.length - filteredRange.length);
filterdTextWithReplacements = [filteredExistingText stringByReplacingCharactersInRange:NSMakeRange(filteredRange.location, filteredRange.length + replacedUnderscores)
withString:filteredReplacementString];
}
} else if (filteredRange.length > filteredReplacementString.length) {
// Net subtraction of characters; replace removed characters with underscores.
NSUInteger neededUnderscores = filteredRange.length - filteredReplacementString.length;
NSMutableString* underscores = [[NSMutableString alloc] initWithCapacity:neededUnderscores];
for (NSUInteger i = 0; i < neededUnderscores; ++i) {
[underscores appendFormat:@"_"];
}
filterdTextWithReplacements = [filteredExistingText stringByReplacingCharactersInRange:NSMakeRange(filteredRange.location, filteredRange.length)
withString:[filteredReplacementString stringByAppendingString:underscores]];
}
NSUInteger expectedDigitCount = 0;
for (NSNumber* digitSetLength in digitSetLengths) {
expectedDigitCount += [digitSetLength unsignedIntegerValue];
}
NSUInteger expectedStringLength = expectedDigitCount + ((digitSetLengths.count - 1) * separator.length);
NSMutableString* paddedFilterdTextWithReplacements = [NSMutableString stringWithString:filterdTextWithReplacements];
if (expectedDigitCount > filterdTextWithReplacements.length) {
for (NSUInteger i = 0, len = expectedDigitCount - filterdTextWithReplacements.length; i < len; ++i) {
[paddedFilterdTextWithReplacements appendString:@"_"];
}
}
NSUInteger index = range.location + replacementString.length;
NSString* textWithReplacementsToIndex = [textWithReplacements substringToIndex:index];
NSUInteger filteredIndex = index - (textWithReplacementsToIndex.length - [textWithReplacementsToIndex stringByRemovingNonDigitsAndUnderscores].length);
NSMutableArray* digitSets = [[NSMutableArray alloc] init];
NSUInteger separatorsBeforeIndex = 0;
BOOL filteredIndexIsAtSeparatorBoundary = NO;
NSUInteger currentDigitSetsDigitStringIndex = 0;
for (NSNumber* _digitSetLength in digitSetLengths) {
NSUInteger digitSetLength = [_digitSetLength unsignedIntegerValue];
[digitSets addObject:[paddedFilterdTextWithReplacements substringWithRange:NSMakeRange(currentDigitSetsDigitStringIndex, digitSetLength)]];
currentDigitSetsDigitStringIndex += digitSetLength;
if (filteredIndex > currentDigitSetsDigitStringIndex) {
++separatorsBeforeIndex;
} else if (filteredIndex == currentDigitSetsDigitStringIndex) {
filteredIndexIsAtSeparatorBoundary = YES;
}
}
if (filteredIndexIsAtSeparatorBoundary && filteredReplacementString.length > filteredRange.length) {
++separatorsBeforeIndex;
}
NSString* text = [digitSets componentsJoinedByString:separator];
textField.text = text;
NSUInteger calcuatedCursorIndex = (separatorsBeforeIndex * separator.length) + filteredIndex;
NSUInteger cursorIndex = MIN(calcuatedCursorIndex, expectedStringLength);
UITextPosition* start = [textField positionFromPosition:[textField beginningOfDocument]
offset:cursorIndex];
UITextPosition* end = [textField positionFromPosition:start
offset:0];
[textField setSelectedTextRange:[textField textRangeFromPosition:start toPosition:end]];
return digitSets;
}
void JCTextFieldSetKeyboardToDigits(UITextField* textField) {
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
textField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
} else {
textField.keyboardType = UIKeyboardTypeDecimalPad;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment