MeasurementFormatter not working as intended - swift

let userDefined = Measurement(value: Double(userInput.text!)!, unit: UnitMass.kilograms)
let calculatedValue = userDefined.converted(to: UnitMass.grams)
print(calculatedValue)
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_US")
Convertedunit.text = formatter.string(from: calculatedValue)
The user input is 5.
The output of print(calculatedValue) is 5000.0g.
However, the output of Convertedunit.text is 11.003lbs which is in pounds. I tried to use different methods, but it is still not in grams. Can anyone enlighten me?

Because of formatter.locale = Locale(identifier: "en_US"), the formatter will automatically convert everything to the imperial unit system, which in this case is pounds. Grams on the other hand belong to the metric system.
There are two ways to change this behaviour:
A) If you want to use the metric system specify a locale for a country that uses it, such as Germany. formatter.locale = Locale(identifier: "de_DE"). Don't worry, this will not affect the language of the string ( such as German: Meter, English: Meters, French: Mètres) as that is still bound to the apps language.
B) If you want to keep whatever unit you put into the formatter simply declare: formatter.unitOptions = .init(arrayLiteral: .providedUnit)
That way the formatter will generate strings with whatever unit you have provided it with.

Related

What is the best way to get a Swift string into a swift-currency/USD format?

How do I get a string into a 'currency' format USD(xxx)?
I am trying to use the following library:
https://github.com/peek-travel/swift-currency
I want to be able to take a string:
var testStr1 = "$30.01"
var testStr2 = "$ 30.01"
and convert this into a currency as I have read from several posts using a double or float is bad, but if I start with a String, what else can I convert it to?
I thought I could use the "import Currency" library to do this, but this is not working.
let updatedString = testStr1.replacingOccurrences(of: "$", with: "")
let formatter = NumberFormatter()
formatter.locale = Locale.current // USA: Locale(identifier: "en_US")
formatter.numberStyle = .decimal
let number = formatter.number(from: test)
var dollars = USD(updatedString)
How do I get a string into a 'currency' format USD(xxx)? If there is a better way to accomplish this?
The basic concept presented by Himanshu works fine, but your problem isn't necessarily making use of an appropriate formatter, but how to fix your input, as the formatter expects a NSNumber and not a String.
So a quick internet check had me looking at Remove all non-numeric characters from a string in swift
So I could take a String, filter out all the "non numerical" junk and then make a Double out of it.
let input = Double(value.filter("0123456789.".contains))
from there I was able to borrow the concept from Himanshu and make a simple format function
func format(_ value: String, locale: Locale = Locale.current) -> String? {
guard let input = Double(value.filter("0123456789.".contains)) else { return nil }
//value.trimmingCharacters(in: .whitespacesAndNewlines)
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = locale
return currencyFormatter.string(from: NSNumber(value: input))
}
I then made use of a Playground to test it using
var testStr1 = "$30.01"
var testStr2 = "$ 30.01"
format(testStr1, locale: Locale(identifier: "en_US")) // $30.01
format(testStr2, locale: Locale(identifier: "en_US")) // $30.01
format(testStr1, locale: Locale(identifier: "fr_FR")) // 30,01 €
format(testStr2, locale: Locale(identifier: "fr_FR")) // 30,01 €
format(testStr1, locale: Locale(identifier: "de_DE")) // 30,01 €
format(testStr2, locale: Locale(identifier: "de_DE")) // 30,01 €
Now, if you specifically want to use USD(xxx) as the format, then you could simply use a basic NumberFormatter and generate your own String from the resulting conversion of the input to a Double
I have read from several posts using a double or float is bad
So, yes, maintaining a currency value as a Double or Float is generally a bad idea, currency values are typically maintained as a Int or Long, but this is due to how Double and Float representation works in computers, for the, general, presentation, you should be fine, but each use case needs be assessed.
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
// localize to your grouping and decimal separator
currencyFormatter.locale = Locale.current
// We'll force unwrap with the !, if you've got defined data you may need more
error checking
let priceString = currencyFormatter.string(from: 9999.99)!
print(priceString) // Displays $9,999.99 in the US locale
**Forcing a Custom Locale**
You can override the users locale to display specific currency formats by changing the Locale using the identifier.
currencyFormatter.locale = Locale(identifier: "fr_FR")
if let priceString = currencyFormatter.string(from: 9999.99) {
print(priceString) // Displays 9 999,99 € in the French locale
}
currencyFormatter.locale = Locale(identifier: "de_DE")
if let priceString = currencyFormatter.string(from: 9999.99) {
print(priceString) // Displays 9.999,99 € in the German locale
}

Date formatter date function returns nil when locale is set to German [duplicate]

Here is the code excerpt:
func mapping(map: Map) {
time <- (map["time"], TransformOf<Date, String>(fromJSON: {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
//dateFormatter.timeZone = TimeZone(abbreviation: "EEST")
if let argument = $0 {
let date = dateFormatter.date(from: argument)
return dateFormatter.date(from: argument)
}
return nil
}}
$0 is string with "22:12:00". I put "let date" to see what it returns and it's nil. I've looked up for format codes here: http://waracle.net/iphone-nsdateformatter-date-formatting-table/
Code should work actually. What am I doing wrong?
EDIT: Added the whole function
EDIT2: I just noticed it's working properly on iPhone 7 iOS 10.1 simulator but returns nil on my iPod 10.1.1 (2016). This is so weird.
From Technical Q&A QA1480 – NSDateFormatter and Internet Dates (emphasis added):
On the other hand, if you're working with fixed-format dates, you should first set the locale of the date formatter to something appropriate for your fixed format. In most cases the best locale to choose is "en_US_POSIX", a locale that's specifically designed to yield US English results regardless of both user and system preferences.
This will prevent the date from being interpreted according to
the user's regional settings:
let dateFormatter = DateFormatter()
// Set the locale first ...
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// ... and then the date format:
dateFormatter.dateFormat = "HH:mm:ss"
// ...
See also What is the best way to deal with the NSDateFormatter locale "feechur"?.

swift 3 can not convert string to date [duplicate]

Here is the code excerpt:
func mapping(map: Map) {
time <- (map["time"], TransformOf<Date, String>(fromJSON: {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
//dateFormatter.timeZone = TimeZone(abbreviation: "EEST")
if let argument = $0 {
let date = dateFormatter.date(from: argument)
return dateFormatter.date(from: argument)
}
return nil
}}
$0 is string with "22:12:00". I put "let date" to see what it returns and it's nil. I've looked up for format codes here: http://waracle.net/iphone-nsdateformatter-date-formatting-table/
Code should work actually. What am I doing wrong?
EDIT: Added the whole function
EDIT2: I just noticed it's working properly on iPhone 7 iOS 10.1 simulator but returns nil on my iPod 10.1.1 (2016). This is so weird.
From Technical Q&A QA1480 – NSDateFormatter and Internet Dates (emphasis added):
On the other hand, if you're working with fixed-format dates, you should first set the locale of the date formatter to something appropriate for your fixed format. In most cases the best locale to choose is "en_US_POSIX", a locale that's specifically designed to yield US English results regardless of both user and system preferences.
This will prevent the date from being interpreted according to
the user's regional settings:
let dateFormatter = DateFormatter()
// Set the locale first ...
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// ... and then the date format:
dateFormatter.dateFormat = "HH:mm:ss"
// ...
See also What is the best way to deal with the NSDateFormatter locale "feechur"?.

Localized date from string returns nil [duplicate]

Here is the code excerpt:
func mapping(map: Map) {
time <- (map["time"], TransformOf<Date, String>(fromJSON: {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
//dateFormatter.timeZone = TimeZone(abbreviation: "EEST")
if let argument = $0 {
let date = dateFormatter.date(from: argument)
return dateFormatter.date(from: argument)
}
return nil
}}
$0 is string with "22:12:00". I put "let date" to see what it returns and it's nil. I've looked up for format codes here: http://waracle.net/iphone-nsdateformatter-date-formatting-table/
Code should work actually. What am I doing wrong?
EDIT: Added the whole function
EDIT2: I just noticed it's working properly on iPhone 7 iOS 10.1 simulator but returns nil on my iPod 10.1.1 (2016). This is so weird.
From Technical Q&A QA1480 – NSDateFormatter and Internet Dates (emphasis added):
On the other hand, if you're working with fixed-format dates, you should first set the locale of the date formatter to something appropriate for your fixed format. In most cases the best locale to choose is "en_US_POSIX", a locale that's specifically designed to yield US English results regardless of both user and system preferences.
This will prevent the date from being interpreted according to
the user's regional settings:
let dateFormatter = DateFormatter()
// Set the locale first ...
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// ... and then the date format:
dateFormatter.dateFormat = "HH:mm:ss"
// ...
See also What is the best way to deal with the NSDateFormatter locale "feechur"?.

How to know if user prefers miles or kilometers?

In my app I've got a certain distance in meters.
And I want to display it in kilometers if user prefers kilometers and display it in miles if user prefers miles. And in the first case I want to add to a string "kilometers" at the end and in the second one to add "miles".
What is the best way to achieve this goal?
Thanks.
To determine whether the user uses metric or not, NSLocale can tell you:
- (BOOL)isMetric {
return [[[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem] boolValue];
}
Swift equivalent of Chris' answer would be something like this:
func isMetric() -> Bool {
return ((Locale.current as NSLocale).object(forKey: NSLocale.Key.usesMetricSystem) as? Bool) ?? true
}
Note that it defaults to true under certain circumstances. Change as needed.
You could ask the user whether they prefer miles or kilometers, in a preference or something. Then whenever you display a distance you would say.
In pseudo c code
function distance(meters) {
if (userPrefersKM) {
return meters / 1000 + " kilometers";
else if (userPrefersMiles) {
return meters / METERS_IN_A_MILE + " miles";
}
Where METERS_IN_A_MILE would be about 1600, but you should look that up.
In Swift, Locale.current.usesMetricSystem gives what the user would expect. But you don't need that if you use Measurement which handles it for you.
let distanceInMeters: Double = 2353.45
let formatter = MeasurementFormatter()
formatter.unitStyle = .medium // adjust according to your need
let distance = Measurement(value: distanceInMeters, unit: UnitLength.meters)
formatter.string(from: distance)
The current locale dictates how it is presented to the user. To see how it works for different locales, try this in a Xcode Playground (examples are for UK and France):
let distanceInMeters: Double = 2353.45
let formatter = MeasurementFormatter()
formatter.unitStyle = .medium // adjust according to your need
let distance = Measurement(value: distanceInMeters, unit: UnitLength.meters)
formatter.locale = Locale(identifier: "en_UK")
formatter.string(from: distance) // 1.462 mi
formatter.locale = Locale(identifier: "en_FR")
formatter.string(from: distance) // 2,353 km
Unless the iPhone provides this information directly, you'll have to have a lookup table from locale to default unit. Then you should allow the user to override that default.