Decimal point in calculations as . or , - ios

If I use decimal pad for input of numbers the decimal changes depending of country and region format.
May be as a point "." or as a comma ","
And I do not have control over at which device the app is used.
If the region format uses a comma the calculation gets wrong. Putting in 5,6 is the the same as putting in only 5 some times and as 56 same times.
And that is even if I programmatically allow both . and , as input in a TextField.
How do I come around this without using the numbers an punctation pad and probably also have to give instructions to avoid input with comma ","
It is only input for numbers and decimal I need and the decimal pad is so much nicer.

You shoudld use a NSNumberFormatter for this, as this can be set to handle different locales.
Create a formatter:
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setLocale:[NSLocale currentLocale]];
Use it:
NSNumber *number = [numberFormatter numberFromString: string]; //string is the textfield.text
if the device's locale is set to a locale, where the decimal separator in a ,, the Number Keypad will use is and the formatter as-well. On those the grouping separator will be .
For the other locales it will be vice-versa.
NSNumberFormatter is very sophisticated, you should read its section in Data Formatter Guide, too. It also knows a lot of currency handling (displaying, not conversion), if your app does handle such.

You can use also the class method of NSNumberFormatter
NSString* formattedString = [NSNumberFormatter
localizedStringFromNumber:number
numberStyle:NSNumberFormatterCurrencyStyle];

Identify is local country uses comma for decimal point
var isUsesCommaForDecimal : Bool {
let nf = NumberFormatter()
nf.locale = Locale.current
let numberLocalized = nf.number(from: "23,34")
if numberLocalized != nil {
return true
} else {
return false
}
}

One way could be to check if the textField contains a ",".
If it contains, replace it with "." and do all arithmetic operations.
As anyways you will be reading all textFields and textViews as NSString object, you can manipulate the input value and transform it according to your need.
Also while showing the result replace "." with "," so that user feel comfortable according to there regional formats.

Related

Is is possible in iOS to have an ascii capable decimal pad as the keyboard type for a UITextfield's keyboard type?

The app I'm writing supports multiple languages but there is 1 textfield where I want the user to write only ascii numbers (i.e. no arabic numbers) and decimal separators.
I know it's possible to set a UITextfield's keyboardType but unfortunately there is a .decimalPad and asciiCapableNumberPad but neither of these suit my needs as asciiCapableNumberPad doesn't have the decimal separator button and decimalPad doesn't restrict it to ascii numbers.
Is it possible to have a asciiCapableDecimalPad? If not, what's the best way to restrict user input to ascii only numbers and decimal separators in a UITextField?
I didn't found any way force keyboard with ASCII number and decimal separator at once. On iPhone 8 when a user has only Arabic keyboard, for .decimalPad keyboard with only Eastern arabic numerals (٠١٢٣٤٥٦٧٨٩) will show, when has multiple keyboard (with eg. English keyboard), the user will have switch button for change different numerals. But this behavior is broken on iPhone X family for me, I think this is the bug on Apple side, the switch button doesn't exist.
I resolved this by implements method textField(_:shouldChangeCharactersIn:replacementString:) and inside this method I replace Eastern arabic numerals (Arabic-Indic?) to ASCII numerals (known also as Western arabic numberals) for validation entering characters during typing.
result = [result stringByReplacingOccurrencesOfString:#"٠" withString:#"0"];
result = [result stringByReplacingOccurrencesOfString:#"١" withString:#"1"];
result = [result stringByReplacingOccurrencesOfString:#"٢" withString:#"2"];
result = [result stringByReplacingOccurrencesOfString:#"٣" withString:#"3"];
result = [result stringByReplacingOccurrencesOfString:#"٤" withString:#"4"];
result = [result stringByReplacingOccurrencesOfString:#"٥" withString:#"5"];
result = [result stringByReplacingOccurrencesOfString:#"٦" withString:#"6"];
result = [result stringByReplacingOccurrencesOfString:#"٧" withString:#"7"];
result = [result stringByReplacingOccurrencesOfString:#"٨" withString:#"8"];
result = [result stringByReplacingOccurrencesOfString:#"٩" withString:#"9"];
result = [result stringByReplacingOccurrencesOfString:#"٫" withString:#"."];
I do the same in textFieldDidEndEditing:. There is the probability this step isn't needed, but I need to format number after losing focus also.
This fix is under testing and I'll let you know if there is any problem with this.
Swift-5 solution for all non-Latin string occurrences.
extension String {
var containsNonEnglishNumbers: Bool {
return !isEmpty && range(of: "[^0-9]", options: .regularExpression) == nil
}
var english: String {
return self.applyingTransform(StringTransform.toLatin, reverse: false) ?? self
}
}
Usage -
textField.addTarget(self, action: #selector(didChangeText(field:)), for: .editingChanged)
#objc func didChangeText(field: UITextField) {
if ((field.text?.containsNonEnglishNumbers) != nil) {
field.text = field.text?.english
}
}

How to use numberFromString when UITextField has currency value in Swift

I have an 'amount' input field that takes a number value that I wish to display as a currency within the input field itself. This I seem to be able to do with no issues.
I then use this value in a calculation and output as a currency value. Again this works fine the first time as it initially sees it as a double and not currency.
My problem comes when I try and reuse the value in the 'amount' input field, as the value is being seen and no longer a double because of the currency symbol etc.
Any suggestions?
Edited below based on suggestions:
var formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
var numberOfPlaces = 2.0
var multiplier = pow(10, numberOfPlaces)
var enteredAmount = formatter.numberFromString(finalAmount.text)?.doubleValue ?? 0.0
enteredAmountLabel.text = formatter.stringFromNumber(enteredAmount)
finalAmount.text = formatter.stringFromNumber(enteredAmount)
Using these amends the calculations work the first time anything is entered into the field. If you try a calculation when the field is populated it resets the field to $0.00
If I amend to:
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
Everything works as expected but the amount input field no longer displays as currency.
I am trying to provide screenshots but I do not have enough reputation points.
Is this the line that gives you trouble?
var enteredAmount = NSNumberFormatter().numberFromString(finalAmount.text)!.doubleValue
It looks like you've got the right idea. You can use a number formatter to convert a value to a formatted string for display, or to convert a formatted string to a value.
I would expect that code to work.
Is that line giving you a compile error? An error at runtime? Not giving the value that it should?
EDIT:
#LeonardoSavioDabus pointed out the problem. That line should use the number formatter you created above:
var enteredAmount = formatter.numberFromString(finalAmount.text)!.doubleValue
And you really shouldn't use force unwrapping like that. You should use the nil coalescing operator:
var enteredAmount = formatter.numberFromString(finalAmount.text)?.doubleValue ?? 0.0
EDIT #2:
Here in the US, this code works perfectly in a playground:
import Foundation
var formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
let moneyString = "$12.93"
var enteredAmount = formatter.numberFromString(moneyString)?.doubleValue ?? 0.0
enteredAmount += 0.07
var newString = formatter.stringFromNumber(enteredAmount)
(I stripped out all the locale stuff to make it simpler.)
EDIT #3:
Your locale code doesn't make sense to me.
for identifier in ["en_US" , "en_UK" , "en_US","fr_FR"] {
formatter.locale = NSLocale(localeIdentifier: identifier)
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
}//end of locale detection for currency and decimal formatting
That code looks like it loops through 3 different values of identifier and assigns 3 different locales to your formatter, and the same max/min digit counts (both 2). The way I read that code, it's going to assign first the US locale, then the UK local, then US English again, and then finally French from France. So at the end of the loop the locale will be set to "fr_FR". That's probably not what you want.

set nslocale for number part of nsstring

Is there any way to set locale on number part of string ?
like if nsstring is like this : "0.510.220" it shows this inside
uilabel view : "۰.۵۱۰.۲۲۰"
i know i can change the nslocale with this if number are valid :
NSDecimalNumber *someNumber = [NSDecimalNumber decimalNumberWithString:#"123"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSLocale *gbLocale = [[[NSLocale alloc] initWithLocaleIdentifier:#"ar"] autorelease];
[formatter setLocale:gbLocale];
but how can i make that happen on full nsstring text? like number inside
string.also im want to make this happen on +7.0 sdk.
No, because what you want is not something that a locale does. "۰.۵۱۰.۲۲۰" is not "0.510.220" in a different locale, it's just simply a different string.
Strings don't have locales. They consist of characters. A string with the characters "0.510.220" has those characters, not others. The Arabic digits are different characters and so you'd need a string with different content.
An NSNumberFormatter is not applying a locale to a string. It's applying a locale to the conversion from a number to a string. That's completely different from what you seem to want.
You could try to parse your string to find a substring that appears to be a number, convert that substring to an actual number using one NSNumberFormatter in one locale, convert the resulting number back to a string using a second NSNumberFormatter in a different locale, and construct a new string by replacing the original substring with the new string.

String characters (marks) are not counted correctlly

Characters as (é) or in arabic (دٌ) are counted as one in a string, how do I make it recognise the mark as a character?
It should be like (د) is a character and (ٌ) is another character.
I don't want to use NSString because I'm using (startIndex) which is not supported in NSString as far as I know.
Thank you
I’m by no means sufficiently knowledgeable in this area to be confident there aren’t some gotchas from this approach, but this appears to do what you’re looking for:
let s = "éدٌ"
let separated = map(s.unicodeScalars) { Character($0) }
println(" , ".join(separated.map(toString)))
// prints "e , ́ , د , ٌ"
Note, if you create a new string from a sequence of those separated characters, it will recompose them:
println(String(separated)) // prints
// prints "éدٌ"

Voice over doesn't read phone number properly

I have phone number in below format
1-1xx-2xx-9565
Currently VO read it as "One (pause) One x x (pause) two x x (pause) minus nine thousand five hundred sixty five".
VO should read it as "One (pause) One x x (pause) two x x (pause) nine five six five".
What could be the problem? Is this wrong phone format?
Let's break down what is happening. VoiceOver doesn't know that the text you are presenting is a phone number and treats it like a sentence of text. In that text it tries to find distinct components and read them appropriately. For example, the text "buy 60 cantaloupes" has 3 components, "buy", "60", and "cantaloupes". The first is text and is read as text, the second is purely numerical and is best read out as "sixty", and the third is read as text.
Applying the same logic to your phone number.
(I'm not talking about actual implementation, just reasoning.)
If you read 1-1xx-2xx-9565 from the left to the right then the first distinct component is "1" which in it self is numerical and is read as "1". If the phone number would have started with "12-1xx" then the first component would have been read as "twelve" because its purely numerical.
The next component is "1xx" or "-1xx" depending on how you look at it. In either case it is a combination of numbers and letters, e.g. it is not purely numerical and is thus read out as text. If you include the "-" in that component is interpreted as a hyphen which isn't read out. That is why the the "-" is never read out for that component. The next component ("-2xx") is treated in the same way.
The final component is "-9565" which turns out to be a valid number. As seen in the cantaloupe sentence, VoiceOver reads this as a number in which case the "-" is no longer interpreted as a hyphen but as a "minus sign".
Getting VoiceOver to read your own text
On any label, view or other element in your application that is used with Voice Over, you can supply your own "accessibility label" when you know more about how you want the text to be read. This is done by simply assigning your own string to the accessibilityLabel property.
Now, you can create a suitable string in many different ways, a very simple one in your case would be to just add spaces everywhere so that each number is read individually. However, it seems a bit fragile to me, so I went ahead and used a number formatter to translate the individual numbers to their textual representations.
NSString *phoneNumber = #"1-1xx-2xx-9565";
// we want to know if a character is a number or not
NSCharacterSet *numberCharacters = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
// we use this formatter to spell out individual numbers
NSNumberFormatter *spellOutSingleNumber = [NSNumberFormatter new];
spellOutSingleNumber.numberStyle = NSNumberFormatterSpellOutStyle;
NSMutableArray *spelledOutComonents = [NSMutableArray array];
// loop over the phone number add add the accessible variants to the array
[phoneNumber enumerateSubstringsInRange:NSMakeRange(0, phoneNumber.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
// check if it's a number
if ([substring rangeOfCharacterFromSet:numberCharacters].location != NSNotFound) {
// is a number
NSNumber *number = #([substring integerValue]);
[spelledOutComonents addObject:[spellOutSingleNumber stringFromNumber:number]];
} else {
// is not a number
[spelledOutComonents addObject:substring];
}
}];
// finally separate the components with spaces (so that the string doesn't become "ninefivesixfive".
NSString *yourAccessiblePhoneNumber = [spelledOutComonents componentsJoinedByString:#" "];
The result when I ran this was
one - one x x - two x x - nine five six five
If you need to do other modifications to your phone numbers to get them to read appropriately then you can do that. I suspect that you will use this is more than one place in your app so creating a custom NSFormatter might be a good idea.
Edit
On iOS 7 you can also use the UIAccessibilitySpeechAttributePunctuation attribute on an attributes string to change how it is pronounced.
Speech Attributes for Attributed Strings
Attributes that you can apply to text in an attributed string to modify how that text is pronounced.
UIAccessibilitySpeechAttributePunctuation
The value of this key is an NSNumber object that you should interpret as a Boolean value. When the value is YES, all punctuation in the text is spoken. You might use this for code or other text where the punctuation is relevant.
Available in iOS 7.0 and later.
Declared in UIAccessibilityConstants.h.
As of iOS 13 you can use a - NSAttributedString.Key.accessibilitySpeechSpellOut as a accessibilityAttributedLabel to make VoiceOver read each letter of the provided string (or a range of string).
So for example:
yourView.accessibilityAttributedLabel = NSAttributedString(string: yourText, attributes: [.accessibilitySpeechSpellOut: true])
If you want to spell all characters individually, a simple solution is to separate the characters by a comma ",".
You can use a String extension to convert the string:
extension String
{
/// Returns string suitable for accessibility (voice over). All characters will be spelled individually.
func stringForSpelling() -> String
{
return stringBySeparatingCharactersWithString(",")
}
/// Inserts a separator between all characters
func stringBySeparatingCharactersWithString(separator: String) -> String
{
var s = ""
// Separate all characters
let chars = self.characters.map({ String($0) })
// Append all characters one by one
for char in chars {
// If there is already a character, append separator before appending next character
if s.characters.count > 0 {
s += separator
}
// Append next character
s += char
}
return s
}
}
And then use it in the code like this:
myLabel.accessibilityLabel = myString.stringForSpelling()
Just Add a comma to each digit of the last number and after the last digit as well,. this will make sure voice over reads the last number as same as previous numbers.
example your number :- 1-1xx-2xx-9565
accessibility label :- 1-1xx-2xx-9,5,6,5,
Here is the code in Swift
public func retrieveAccessiblePhoneNumber(phoneNumber: String) -> String {
// We want to know if a character is a number or not
let characterSet = NSCharacterSet(charactersInString: "0123456789")
// We use this formatter to spell out individual numbers
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .SpellOutStyle
var spelledOutComponents = [String]()
let range = Range<String.Index>(start: phoneNumber.startIndex, end: phoneNumber.endIndex)
// Loop over the phone number add add the accessible variants to the array
phoneNumber.enumerateSubstringsInRange(range,
options: NSStringEnumerationOptions.ByComposedCharacterSequences) { (substring, substringRange, enclosingRange, stop) -> () in
// Check if it's a number
if let substr = substring where substr.rangeOfCharacterFromSet(characterSet) != nil {
if let number = Int(substr) {
// Is a number
let nsNumber = NSNumber(integer: number)
spelledOutComponents.append(numberFormatter.stringFromNumber(nsNumber)!)
}
} else {
// Is not a number
spelledOutComponents.append(substring!)
}
}
// Finally separate the components with spaces (so that the string doesn't become "ninefivesixfive".
return spelledOutComponents.joinWithSeparator(" ")
}

Resources