Skip to content

Commit

Permalink
Support different locales.
Browse files Browse the repository at this point in the history
  • Loading branch information
ayanonagon committed Mar 21, 2014
1 parent f4626a9 commit ec06eda
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 46 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
------

Expand Down
6 changes: 6 additions & 0 deletions VENCalculatorInputView/VENCalculatorInputTextField.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@

@interface VENCalculatorInputTextField : UITextField <VENCalculatorInputViewDelegate>

/**
The locale to use for the decimal separator.
Defaults to locale for current device.
*/
@property (strong, nonatomic) NSLocale *locale;

@end
41 changes: 34 additions & 7 deletions VENCalculatorInputView/VENCalculatorInputTextField.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#import "VENCalculatorInputTextField.h"
#import "VENMoneyCalculator.h"

@interface VENCalculatorInputTextField ()
@property (strong, nonatomic) VENMoneyCalculator *moneyCalculator;
@end

@implementation VENCalculatorInputTextField

- (id)initWithFrame:(CGRect)frame {
Expand All @@ -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 {
Expand All @@ -34,23 +57,23 @@ - (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;
}
}
}

- (void)venCalculatorTextFieldDidEndEditing {
NSString *textToEvaluate = [self trimExpressionString:self.text];
NSString *evaluatedString = [VENMoneyCalculator evaluateExpression:textToEvaluate];
NSString *evaluatedString = [self.moneyCalculator evaluateExpression:textToEvaluate];
if (evaluatedString) {
self.text = evaluatedString;
}
Expand Down Expand Up @@ -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
12 changes: 10 additions & 2 deletions VENCalculatorInputView/VENCalculatorInputView.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@

@property (weak, nonatomic) id<VENCalculatorInputViewDelegate> 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;


/**-----------------------------------------------------------------------------
Expand Down
17 changes: 17 additions & 0 deletions VENCalculatorInputView/VENCalculatorInputView.m
Original file line number Diff line number Diff line change
@@ -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]];
Expand All @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions VENCalculatorInputView/VENCalculatorInputView.xib
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="decimalButton" destination="eYd-gM-or0" id="Lng-xU-k41"/>
<outletCollection property="numberButtonCollection" destination="2Ty-om-JCT" id="YQr-Vb-a62"/>
<outletCollection property="operationButtonCollection" destination="WE3-xD-kgf" id="m1z-hQ-z5E"/>
<outletCollection property="numberButtonCollection" destination="i9h-WF-taO" id="kgO-g8-e3f"/>
Expand Down
4 changes: 3 additions & 1 deletion VENCalculatorInputView/VENMoneyCalculator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 34 additions & 5 deletions VENCalculatorInputView/VENMoneyCalculator.m
Original file line number Diff line number Diff line change
@@ -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];
Expand All @@ -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;
Expand All @@ -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
4 changes: 2 additions & 2 deletions VENCalculatorInputViewSample/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PODS:
- VENCalculatorInputView (1.1.0)
- VENCalculatorInputView (1.1.1)

DEPENDENCIES:
- VENCalculatorInputView (from `./../VENCalculatorInputView.podspec`)
Expand All @@ -9,6 +9,6 @@ EXTERNAL SOURCES:
:path: ./../VENCalculatorInputView.podspec

SPEC CHECKSUMS:
VENCalculatorInputView: ed89b1b2adc0f2394143353f17ba0cd4456a5ab3
VENCalculatorInputView: 47cec0bab2fe3f9140fe1613d7ae33df3979b1d3

COCOAPODS: 0.29.0
86 changes: 57 additions & 29 deletions VENCalculatorInputViewTests/VENMoneyCalculatorSpecs.m
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});

});
Expand Down

0 comments on commit ec06eda

Please sign in to comment.