Use MeasurementFormatter to display meters as feet - ios

I'm having trouble getting the new MeasurementFormatter class to give me results in appropriate units. I have values in meters that I want to display in localized strings, either meters or feet depending on the user's locale.
I have this:
let meters : Double = 10
let metersMeasurement = Measurement(value: meters, unit: UnitLength.meters)
let measurementFormatter = MeasurementFormatter()
measurementFormatter.locale = Locale(identifier: "en_US")
let localizedString = measurementFormatter.string(from: metersMeasurement)
This gives me a string of "0.006mi". It's correct, I guess, but converting 10 meters to miles is kind of ridiculous. What I want is "32.8ft".
The .providedUnit option on MeasurementFormatter isn't helpful-- that just gives me a result in meters.
I could look up the current locale and handle this myself, but that's exactly the kind of thing that MeasurementFormatter is supposed to make unnecessary. Is there some way to get it to do what I need?

You should set the formatter's unitOptions to naturalScale:
let measurementFormatter = MeasurementFormatter()
measurementFormatter.unitOptions = .naturalScale
And you should only set the locale if you want every user in any locale to see the value in the specific locale.

Related

Controlling accessibility voice over readout of numbers in iOS

I'm trying to get an accessibilityValue with a decimal number on a custom UIView to readout as "twenty point one", for example, similar to how voice over reads out the duration and keyframe values on the video trimmer when editing a video in the Photos app.
The default setup reads out the value as "twenty dot one". If I set the accessibilityAttributedLabel instead using the accessibilitySpeechPunctuation key, it reads as "twenty period one".
view.accessibilityAttributedLabel = NSAttributedString(string: "20.1", attributes: [.accessibilitySpeechPunctuation: true])
Without resorting to manually building a numeric string to read out, anyone know how to get the number to read saying "point" instead of "dot" or "period"?
Got it! Formatting a number using a NumberFormatter with a style of .spellOut will generate a string with the fully spelled out value. Not what we want for a label's text, but exactly what we want for an accessibility label.
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
let label = UILabel()
label.text = formatter.string(from: 20.1)
label.accessibilityLabel = formatter.string(from: 20.1)
// prints out "twenty point one"
print(label.accessibilityLabel)

"%.2f" is rounding my number up instead of cutting

I have the following number
7.9775609756097534
and I'm using the code below to only show two decimals
let formatted = String(format: "Angle: %.2f", angle)
the problem is that the result is:
7.98
instead of
7.97
For cases such as yours we use NumberFormatter. It is a class designed to do what you need and more. For your case it should be enough to use the following:
let numberFormatter = NumberFormatter()
numberFormatter.roundingMode = .down
numberFormatter.minimumFractionDigits = 2
numberFormatter.maximumFractionDigits = 2
numberFormatter.string(from: 1.236)
This now locks fraction digits to be exactly 2. By increasing minimum fraction digits more "0" may be appended as in 0.10 may become 0.100. Maximum fraction digits will simply restrict up to what point the number will be displayed.
There are other options as well such as making 1234567.89 show as 1.234.567,89 which is really nice for users that are used to such formatting.
Alternatively, you can do bit string manipulation
let numberInFloat:Float = 7.9775609756097534
let numberInString: String = String(format: "%f", numberInFloat)
let numberParts = numberInString.components(separatedBy: ".")
print(String(format: "Output: %#.%#", String(numberParts[0]), String(numberParts[1].prefix(2))))
Output: 7.97

Swift NumberFormatter formatting all values to 3 decimal places?

I have a large array of doubles, which have a varying number of decimal places, such as:
[11307.3, 1025.64, 1.27826, 1676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604]
I am attempting to format the number so there are commas every thousand and the decimal places are not formatted to a specific number such as 3dp. The array should look like
[11,307.3, 1,025.64, 1.27826, 1,676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604]
I have tried doing this by defining NumberFormatter as such:
let numberFormatter = NumberFormatter()
and then choosing decimal for style:
numberFormatter.numberStyle = NumberFormatter.Style.decimal
The values in the array are display in a table view, and when a user taps on for example the 2nd cell, in a new screen the value 1,025.64 would be displayed.
I used this code to do that:
var formattedPrice = numberFormatter.string(from: NSNumber(value:coinPriceDouble!))
self.coinPriceLbl.text = "\(coinTitleText!): \(Cryptocoin.instance.fiatSymbol)\(formattedPrice!)"
This works perfect for any value that does not have more than 3 decimal places.
If the user chose the 3rd value in the array, it would display 1.278 not 1.27826.
Is there any way to format these values with commas but not force them to a specific number of decimal places?
As vadian said, NumberFormatter is highly customisable.
Just play around its properties, like (you need to customise based on your needs):
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 3
Here the explanation for NumberFormatter's maximumFractionDigits property and related.
Here instead a blog that explains all the related aspects of NumberFormatter A Guide to NSNumberFormatter.
EDIT
Put the following code in a Playground and observe the result:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 3
let formattedNumbers = [11307.3, 1025.64, 1.27826, 1676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604].flatMap { number in
return numberFormatter.string(from: number)
}
print(formattedNumbers)
Link: https://stackoverflow.com/a/27571946/6655075 .
This solved my problem. As I had 3 values displaying, each from a different array, I would end up formatting all 3 whereas I only wanted to format 1 array.
extension Double {
static let twoFractionDigits: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
var formatted: String {
return Double.twoFractionDigits.string(for: self) ?? ""
}
}
I removed
var formattedPrice = numberFormatter.string(from: NSNumber(value:coinPriceDouble!))
And simply used
self.coinPriceLbl.text = "\(coinTitleText!): \(Cryptocoin.instance.fiatSymbol)\(coinPriceDouble!.formatted)"
Edit: As Dávid Pásztor mentioned, I only want to add the comma separator to the values which need it while still maintaining the precision of each value down to the last decimal value.
You could try setting the maximum fraction digits to a largish number.
numberFormatter.maximumFractionDigits = 15

String to Double conversion loses precision in Swift

I want to covert a string to double and keep the same value:
let myStr = "2.40"
let numberFormatter = NSNumberFormatter()
numberFormatter.locale = NSLocale(localeIdentifier: "fr_FR")
let myDouble = numberFormatter.numberFromString(myStr)?.doubleValue ?? 0.0
myDouble is now
Double? 2.3999999999999999
So how to convert "2.40" to exact be 2.40 as Double ??
Update:
Even rounding after conversion does not seem to work
I don't want to print, I want to calculate and it's important that the number should be correct, it's Money calculation and rates
First off: you don't! What you encountered here is called floating point inaccuracy. Computers cannot store every number precisely. 2.4 cannot be stored lossless within a floating point type.
Secondly: Since floating point is always an issue and you are dealing with money here (I guess you are trying to store 2.4 franc) your number one solution is: don't use floating point numbers. Use the NSNumber you get from the numberFromString and do not try to get a Double out of it.
Alternatively shift the comma by multiplying and store it as Int.
The first solutions might look something like:
if let num = myDouble {
let value = NSDecimalNumber(decimal: num.decimalValue)
let output = value.decimalNumberByMultiplyingBy(NSDecimalNumber(integer: 10))
}

NSDecimalNumber issues

In my iOS swift application, I receive some json from the web which contains some double values which represent currency. It looks like this:
[{"Amount": 5.0},{"Amount":-26.07},{"Amount": 4}, ...etc]
I cast these as Doubles and then try to feed these values as a Swift "Double" into the NSDecimalNumber's constructor like this:
let amount = NSDecimalNumber(double: amountAsDouble)
I'm running into problems with this approach because very frequently the NSDecimalNumber I created will contain a different number that goes 16 places passed the decimal point.
let amount = NSDecimalNumber(double: -15.97)
println(amount)
this returns -15.970000000000004096
I don't want this, I want -15.97.
Thanks,
A Double is stored with 18 decimal digits, you can't do anything about that, it's how it works.
Read here: http://en.wikipedia.org/wiki/Double-precision_floating-point_format
However, at the time of displaying the value on the screen, you can use NSNumberFormatter like this:
let amountInDouble: Double = -15.970000000000004096
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.roundingIncrement = 0.01
formatter.maximumFractionDigits = 2
let amountAsString = formatter.stringFromNumber(NSNumber(double: amountInDouble))
if let amountAsString = amountAsString {
println(amountAsString) // -15.97
}
I recently went through this for myself. I ended up using an NSNumberFormatter to get the proper decimal places.
let currFormatter = NSNumberFormatter()
currFormatter.numberStyle = .DecimalStyle
currFormatter.roundingIncrement = 0.01
currFormatter.minimumFractionDigits = 2
currFormatter.maximumFractionDigits = 2
let doubleAmount = currFormatter.numberFromString(amountAsDouble) as NSNumber!
let amount = doubleAmount as Double
println(amount)
Here's a tip: If you use NSJSONSerializer, numbers with decimal points are actually turned into NSDecimalNumber for you. NSDecimalNumber is a subclass of NSNumber. So what you are doing: You've got a perfectly fine NSDecimalNumber, round the value to double, and try to turn the double back into an NSDecimalNumber. Just check that what you have is indeed an NSDecimalNumber, and do no conversion if it is.
This is because the intermediate double representation is causing problems.
You should take the values from your dictionary as NSString objects and use the + decimalNumberWithString: method to convert without losing precision. In swift:
let amount = NSDecimalNumber(string: amountAsString)
let amount = NSDecimalNumber.init(value: -15.97)
let roundValue = amount.rounding(accordingToBehavior: NSDecimalNumberHandler(roundingMode: .bankers, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false))
print(roundValue)

Resources