From ec06eda540839054a94d6ed27a3e3cffa880fa0e Mon Sep 17 00:00:00 2001 From: Ayaka Nonaka Date: Fri, 21 Mar 2014 11:38:59 -0700 Subject: [PATCH] Support different locales. --- README.md | 5 ++ .../VENCalculatorInputTextField.h | 6 ++ .../VENCalculatorInputTextField.m | 41 +++++++-- .../VENCalculatorInputView.h | 12 ++- .../VENCalculatorInputView.m | 17 ++++ .../VENCalculatorInputView.xib | 1 + VENCalculatorInputView/VENMoneyCalculator.h | 4 +- VENCalculatorInputView/VENMoneyCalculator.m | 39 +++++++-- VENCalculatorInputViewSample/Podfile.lock | 4 +- .../VENMoneyCalculatorSpecs.m | 86 ++++++++++++------- 10 files changed, 169 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 4f00e20..f6081ea 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,11 @@ You can read more about custom keyboards in [Apple's documentation](https://deve All you need to do is use ```VENCalculatorInputTextField``` instead of ```UITextField``` and use it like normal text field. It will automagically handle the input and make calculations. Take a look at out our ```VENCalculatorInputViewSample``` project. +Localization +------ + +Different regions use different symbols as their decimal separator. By default, ```VENCalculatorInputView``` and ```VENCalculatorInputTextField``` use the current locale of the device. You can change it by setting the ```locale``` property. + Testing ------ diff --git a/VENCalculatorInputView/VENCalculatorInputTextField.h b/VENCalculatorInputView/VENCalculatorInputTextField.h index 73c95fa..a8bfccd 100644 --- a/VENCalculatorInputView/VENCalculatorInputTextField.h +++ b/VENCalculatorInputView/VENCalculatorInputTextField.h @@ -3,4 +3,10 @@ @interface VENCalculatorInputTextField : UITextField +/** + The locale to use for the decimal separator. + Defaults to locale for current device. + */ +@property (strong, nonatomic) NSLocale *locale; + @end diff --git a/VENCalculatorInputView/VENCalculatorInputTextField.m b/VENCalculatorInputView/VENCalculatorInputTextField.m index 3901ac6..c856003 100644 --- a/VENCalculatorInputView/VENCalculatorInputTextField.m +++ b/VENCalculatorInputView/VENCalculatorInputTextField.m @@ -1,6 +1,10 @@ #import "VENCalculatorInputTextField.h" #import "VENMoneyCalculator.h" +@interface VENCalculatorInputTextField () +@property (strong, nonatomic) VENMoneyCalculator *moneyCalculator; +@end + @implementation VENCalculatorInputTextField - (id)initWithFrame:(CGRect)frame { @@ -16,13 +20,32 @@ - (void)awakeFromNib { } - (void)setUpInit { - self.inputView = [[VENCalculatorInputView alloc] init]; - ((VENCalculatorInputView *)self.inputView).delegate = self; + self.locale = [NSLocale currentLocale]; + + VENCalculatorInputView *inputView = [VENCalculatorInputView new]; + inputView.delegate = self; + inputView.locale = self.locale; + self.inputView = inputView; + + VENMoneyCalculator *moneyCalculator = [VENMoneyCalculator new]; + moneyCalculator.locale = self.locale; + self.moneyCalculator = moneyCalculator; + [self addTarget:self action:@selector(venCalculatorTextFieldDidChange) forControlEvents:UIControlEventEditingChanged]; [self addTarget:self action:@selector(venCalculatorTextFieldDidEndEditing) forControlEvents:UIControlEventEditingDidEnd]; } +#pragma mark - Properties + +- (void)setLocale:(NSLocale *)locale { + _locale = locale; + VENCalculatorInputView *inputView = self.inputView; + inputView.locale = locale; + self.moneyCalculator.locale = locale; +} + + #pragma mark - UITextField - (void)venCalculatorTextFieldDidChange { @@ -34,15 +57,15 @@ - (void)venCalculatorTextFieldDidChange { [lastCharacterString isEqualToString:@"−"] || [lastCharacterString isEqualToString:@"×"] || [lastCharacterString isEqualToString:@"÷"]) { - NSString *evaluatedString = [VENMoneyCalculator evaluateExpression:subString]; + NSString *evaluatedString = [self.moneyCalculator evaluateExpression:subString]; if (evaluatedString) { self.text = [NSString stringWithFormat:@"%@%@", evaluatedString, lastCharacterString]; } else { self.text = subString; } - } else if ([lastCharacterString isEqualToString:@"."]) { + } else if ([lastCharacterString isEqualToString:[self decimalSeparator]]) { NSString *secondToLastCharacterString = [self.text substringWithRange:NSMakeRange([self.text length] - 2, 1)]; - if ([secondToLastCharacterString isEqualToString:@"."]) { + if ([secondToLastCharacterString isEqualToString:[self decimalSeparator]]) { self.text = subString; } } @@ -50,7 +73,7 @@ - (void)venCalculatorTextFieldDidChange { - (void)venCalculatorTextFieldDidEndEditing { NSString *textToEvaluate = [self trimExpressionString:self.text]; - NSString *evaluatedString = [VENMoneyCalculator evaluateExpression:textToEvaluate]; + NSString *evaluatedString = [self.moneyCalculator evaluateExpression:textToEvaluate]; if (evaluatedString) { self.text = evaluatedString; } @@ -82,11 +105,15 @@ - (NSString *)trimExpressionString:(NSString *)expressionString { [lastCharacterString isEqualToString:@"−"] || [lastCharacterString isEqualToString:@"×"] || [lastCharacterString isEqualToString:@"÷"] || - [lastCharacterString isEqualToString:@"."]) { + [lastCharacterString isEqualToString:[self decimalSeparator]]) { return [self.text substringToIndex:self.text.length - 1]; } } return expressionString; } +- (NSString *)decimalSeparator { + return [self.locale objectForKey:NSLocaleDecimalSeparator]; +} + @end diff --git a/VENCalculatorInputView/VENCalculatorInputView.h b/VENCalculatorInputView/VENCalculatorInputView.h index 1b88df7..a32cf9d 100644 --- a/VENCalculatorInputView/VENCalculatorInputView.h +++ b/VENCalculatorInputView/VENCalculatorInputView.h @@ -13,8 +13,16 @@ @property (weak, nonatomic) id delegate; -@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *numberButtonCollection; -@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *operationButtonCollection; +/**----------------------------------------------------------------------------- + * @name Localization + * ----------------------------------------------------------------------------- + */ + +/** + The locale to use for the decimal separator. + Defaults to locale for current device. + */ +@property (strong, nonatomic) NSLocale *locale; /**----------------------------------------------------------------------------- diff --git a/VENCalculatorInputView/VENCalculatorInputView.m b/VENCalculatorInputView/VENCalculatorInputView.m index beacc76..2a5b8a4 100644 --- a/VENCalculatorInputView/VENCalculatorInputView.m +++ b/VENCalculatorInputView/VENCalculatorInputView.m @@ -1,10 +1,21 @@ #import "VENCalculatorInputView.h" +@interface VENCalculatorInputView () + +@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *numberButtonCollection; +@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *operationButtonCollection; +@property (strong, nonatomic) IBOutlet UIButton *decimalButton; + +@end + @implementation VENCalculatorInputView - (id)initWithFrame:(CGRect)frame { self = [[[NSBundle mainBundle] loadNibNamed:@"VENCalculatorInputView" owner:self options:nil] firstObject]; if (self) { + // Set default locale + self.locale = [NSLocale currentLocale]; + // Set customizable properties [self setNumberButtonBackgroundColor:[UIColor colorWithWhite:0.98828 alpha:1]]; [self setNumberButtonBorderColor:[UIColor colorWithRed:193/255.0f green:195/255.0f blue:199/255.0f alpha:1]]; @@ -24,6 +35,12 @@ - (id)initWithFrame:(CGRect)frame { return self; } +- (void)setLocale:(NSLocale *)locale { + _locale = locale; + NSString *decimalSymbol = [locale objectForKey:NSLocaleDecimalSeparator]; + [self.decimalButton setTitle:decimalSymbol forState:UIControlStateNormal]; +} + - (void)setupButton:(UIButton *)button { button.layer.borderWidth = 0.25f; } diff --git a/VENCalculatorInputView/VENCalculatorInputView.xib b/VENCalculatorInputView/VENCalculatorInputView.xib index 682845c..a13bc69 100644 --- a/VENCalculatorInputView/VENCalculatorInputView.xib +++ b/VENCalculatorInputView/VENCalculatorInputView.xib @@ -207,6 +207,7 @@ + diff --git a/VENCalculatorInputView/VENMoneyCalculator.h b/VENCalculatorInputView/VENMoneyCalculator.h index efa0e31..93a3aa9 100644 --- a/VENCalculatorInputView/VENMoneyCalculator.h +++ b/VENCalculatorInputView/VENMoneyCalculator.h @@ -2,11 +2,13 @@ @interface VENMoneyCalculator : NSObject +@property (strong, nonatomic) NSLocale *locale; + /** * Evaluates a mathematical expression containing +, −, ×, and ÷. * @param expression The expression to evaluate * @return The evaluated expression. Returns nil if the expression is invalid. */ -+ (NSString *)evaluateExpression:(NSString *)expression; +- (NSString *)evaluateExpression:(NSString *)expression; @end diff --git a/VENCalculatorInputView/VENMoneyCalculator.m b/VENCalculatorInputView/VENMoneyCalculator.m index c1422db..86ad89f 100644 --- a/VENCalculatorInputView/VENMoneyCalculator.m +++ b/VENCalculatorInputView/VENMoneyCalculator.m @@ -1,13 +1,25 @@ #import "VENMoneyCalculator.h" +@interface VENMoneyCalculator () +@property (strong, nonatomic) NSNumberFormatter *numberFormatter; +@end + @implementation VENMoneyCalculator -+ (NSString *)evaluateExpression:(NSString *)expressionString { +- (instancetype)init { + self = [super init]; + if (self) { + self.locale = [NSLocale currentLocale]; + } + return self; +} + +- (NSString *)evaluateExpression:(NSString *)expressionString { if (!expressionString) { return nil; } NSString *floatString = [NSString stringWithFormat:@"1.0*%@", expressionString]; - NSString *sanitizedString = [VENMoneyCalculator replaceOperandsInString:floatString]; + NSString *sanitizedString = [self replaceOperandsInString:floatString]; NSExpression *expression; @try { expression = [NSExpression expressionWithFormat:sanitizedString]; @@ -28,7 +40,8 @@ + (NSString *)evaluateExpression:(NSString *)expressionString { } else if (floatExpression >= CGFLOAT_MAX || floatExpression <= CGFLOAT_MIN || isnan(floatExpression)) { return @"0"; } else { - return [NSString stringWithFormat:@"%.2f", floatExpression]; + NSString *moneyFormattedNumber = [[self numberFormatter] stringFromNumber:@(floatExpression)]; + return [moneyFormattedNumber stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } } else { return nil; @@ -38,10 +51,26 @@ + (NSString *)evaluateExpression:(NSString *)expressionString { #pragma mark - Private -+ (NSString *)replaceOperandsInString:(NSString *)string { +- (NSNumberFormatter *)numberFormatter { + if (!_numberFormatter) { + _numberFormatter = [NSNumberFormatter new]; + [_numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; + [_numberFormatter setCurrencySymbol:@""]; + [_numberFormatter setCurrencyDecimalSeparator:[self decimalSeparator]]; + } + return _numberFormatter; +} + +- (NSString *)replaceOperandsInString:(NSString *)string { NSString *subtractReplaced = [string stringByReplacingOccurrencesOfString:@"−" withString:@"-"]; NSString *divideReplaced = [subtractReplaced stringByReplacingOccurrencesOfString:@"÷" withString:@"/"]; - return [divideReplaced stringByReplacingOccurrencesOfString:@"×" withString:@"*"]; + NSString *multiplyReplaced = [divideReplaced stringByReplacingOccurrencesOfString:@"×" withString:@"*"]; + + return [multiplyReplaced stringByReplacingOccurrencesOfString:[self decimalSeparator] withString:@"."]; +} + +- (NSString *)decimalSeparator { + return [self.locale objectForKey:NSLocaleDecimalSeparator]; } @end diff --git a/VENCalculatorInputViewSample/Podfile.lock b/VENCalculatorInputViewSample/Podfile.lock index 7b3e1c9..b916f3b 100644 --- a/VENCalculatorInputViewSample/Podfile.lock +++ b/VENCalculatorInputViewSample/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - VENCalculatorInputView (1.1.0) + - VENCalculatorInputView (1.1.1) DEPENDENCIES: - VENCalculatorInputView (from `./../VENCalculatorInputView.podspec`) @@ -9,6 +9,6 @@ EXTERNAL SOURCES: :path: ./../VENCalculatorInputView.podspec SPEC CHECKSUMS: - VENCalculatorInputView: ed89b1b2adc0f2394143353f17ba0cd4456a5ab3 + VENCalculatorInputView: 47cec0bab2fe3f9140fe1613d7ae33df3979b1d3 COCOAPODS: 0.29.0 diff --git a/VENCalculatorInputViewTests/VENMoneyCalculatorSpecs.m b/VENCalculatorInputViewTests/VENMoneyCalculatorSpecs.m index 4096d4e..1102435 100644 --- a/VENCalculatorInputViewTests/VENMoneyCalculatorSpecs.m +++ b/VENCalculatorInputViewTests/VENMoneyCalculatorSpecs.m @@ -7,61 +7,89 @@ SpecBegin(VENMoneyCalculator) describe(@"Evaluate expressions", ^{ + __block VENMoneyCalculator *moneyCalculator; + + beforeAll(^{ + moneyCalculator = [VENMoneyCalculator new]; + moneyCalculator.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"]; + }); it(@"should handle addition", ^{ - expect([VENMoneyCalculator evaluateExpression:@"1+1"]).to.equal(@"2"); - expect([VENMoneyCalculator evaluateExpression:@"1 + 1"]).to.equal(@"2"); - expect([VENMoneyCalculator evaluateExpression:@"1 + 1000"]).to.equal(@"1001"); + expect([moneyCalculator evaluateExpression:@"1+1"]).to.equal(@"2"); + expect([moneyCalculator evaluateExpression:@"1 + 1"]).to.equal(@"2"); + expect([moneyCalculator evaluateExpression:@"1 + 1000"]).to.equal(@"1001"); }); it(@"should handle subtraction", ^{ - expect([VENMoneyCalculator evaluateExpression:@"1-1"]).to.equal(@"0"); - expect([VENMoneyCalculator evaluateExpression:@"10000-1"]).to.equal(@"9999"); - expect([VENMoneyCalculator evaluateExpression:@"0 - 100"]).to.equal(@"-100"); + expect([moneyCalculator evaluateExpression:@"1-1"]).to.equal(@"0"); + expect([moneyCalculator evaluateExpression:@"10000-1"]).to.equal(@"9999"); + expect([moneyCalculator evaluateExpression:@"0 - 100"]).to.equal(@"-100"); }); it(@"should handle multiplication", ^{ - expect([VENMoneyCalculator evaluateExpression:@"2*2"]).to.equal(@"4"); - expect([VENMoneyCalculator evaluateExpression:@"100*1.2"]).to.equal(@"120"); - expect([VENMoneyCalculator evaluateExpression:@"1000 * 0.8"]).to.equal(@"800"); + expect([moneyCalculator evaluateExpression:@"2*2"]).to.equal(@"4"); + expect([moneyCalculator evaluateExpression:@"100*1.2"]).to.equal(@"120"); + expect([moneyCalculator evaluateExpression:@"1000 * 0.8"]).to.equal(@"800"); }); it(@"should handle division", ^{ - expect([VENMoneyCalculator evaluateExpression:@"2/2"]).to.equal(@"1"); - expect([VENMoneyCalculator evaluateExpression:@"100/4"]).to.equal(@"25"); - expect([VENMoneyCalculator evaluateExpression:@"1/2"]).to.equal(@"0.50"); + expect([moneyCalculator evaluateExpression:@"2/2"]).to.equal(@"1"); + expect([moneyCalculator evaluateExpression:@"100/4"]).to.equal(@"25"); + expect([moneyCalculator evaluateExpression:@"1/2"]).to.equal(@"0.50"); }); it(@"should return nil for invalid expressions", ^{ - expect([VENMoneyCalculator evaluateExpression:@"1+++1"]).to.beNil(); - expect([VENMoneyCalculator evaluateExpression:@"++++-12!@#"]).to.beNil(); - expect([VENMoneyCalculator evaluateExpression:@"+"]).to.beNil(); - expect([VENMoneyCalculator evaluateExpression:@"1+"]).to.beNil(); + expect([moneyCalculator evaluateExpression:@"1+++1"]).to.beNil(); + expect([moneyCalculator evaluateExpression:@"++++-12!@#"]).to.beNil(); + expect([moneyCalculator evaluateExpression:@"+"]).to.beNil(); + expect([moneyCalculator evaluateExpression:@"1+"]).to.beNil(); }); it(@"should handle − (longer dash)", ^{ - expect([VENMoneyCalculator evaluateExpression:@"1−1"]).to.equal(@"0"); - expect([VENMoneyCalculator evaluateExpression:@"10000−1"]).to.equal(@"9999"); - expect([VENMoneyCalculator evaluateExpression:@"0 − 100"]).to.equal(@"-100"); + expect([moneyCalculator evaluateExpression:@"1−1"]).to.equal(@"0"); + expect([moneyCalculator evaluateExpression:@"10000−1"]).to.equal(@"9999"); + expect([moneyCalculator evaluateExpression:@"0 − 100"]).to.equal(@"-100"); }); it(@"should handle ×", ^{ - expect([VENMoneyCalculator evaluateExpression:@"2×2"]).to.equal(@"4"); - expect([VENMoneyCalculator evaluateExpression:@"100×1.2"]).to.equal(@"120"); - expect([VENMoneyCalculator evaluateExpression:@"1000 × 0.8"]).to.equal(@"800"); + expect([moneyCalculator evaluateExpression:@"2×2"]).to.equal(@"4"); + expect([moneyCalculator evaluateExpression:@"100×1.2"]).to.equal(@"120"); + expect([moneyCalculator evaluateExpression:@"1000 × 0.8"]).to.equal(@"800"); }); it(@"should handle ÷", ^{ - expect([VENMoneyCalculator evaluateExpression:@"2÷2"]).to.equal(@"1"); - expect([VENMoneyCalculator evaluateExpression:@"100÷4"]).to.equal(@"25"); - expect([VENMoneyCalculator evaluateExpression:@"1÷2"]).to.equal(@"0.50"); + expect([moneyCalculator evaluateExpression:@"2÷2"]).to.equal(@"1"); + expect([moneyCalculator evaluateExpression:@"100÷4"]).to.equal(@"25"); + expect([moneyCalculator evaluateExpression:@"1÷2"]).to.equal(@"0.50"); }); it(@"should handle ÷ 0", ^{ - expect([VENMoneyCalculator evaluateExpression:@"2÷0"]).to.equal(@"0"); - expect([VENMoneyCalculator evaluateExpression:@"0÷0"]).to.equal(@"0"); - expect([VENMoneyCalculator evaluateExpression:@"-2÷0"]).to.equal(@"0"); - expect([VENMoneyCalculator evaluateExpression:@"-0÷0"]).to.equal(@"0"); + expect([moneyCalculator evaluateExpression:@"2÷0"]).to.equal(@"0"); + expect([moneyCalculator evaluateExpression:@"0÷0"]).to.equal(@"0"); + expect([moneyCalculator evaluateExpression:@"-2÷0"]).to.equal(@"0"); + expect([moneyCalculator evaluateExpression:@"-0÷0"]).to.equal(@"0"); + }); + +}); + + +describe(@"Handle other locale", ^{ + __block VENMoneyCalculator *moneyCalculator; + + beforeAll(^{ + moneyCalculator = [VENMoneyCalculator new]; + moneyCalculator.locale = [NSLocale localeWithLocaleIdentifier:@"fr_FR"]; + }); + + it(@"should handle division", ^{ + expect([moneyCalculator evaluateExpression:@"2/2"]).to.equal(@"1"); + expect([moneyCalculator evaluateExpression:@"100/4"]).to.equal(@"25"); + expect([moneyCalculator evaluateExpression:@"1/2"]).to.equal(@"0,50"); + }); + + it(@"should handle ×", ^{ + expect([moneyCalculator evaluateExpression:@"100×1,2"]).to.equal(@"120"); + expect([moneyCalculator evaluateExpression:@"1000 × 0,8"]).to.equal(@"800"); }); });