Choosing units with MeasurementFormatter - ios

This is similar to a question I asked yesterday but the answer I got doesn't seem to work in this case.
I'm getting altitude values in meters from Core Location. I want to display these in a localized form. As an example, the altitude where I am right now is 1839m above sea level. This should be displayed as 6033 feet. The best I can do with MeasurementFormatter is "1.143 mi".
let meters : Double = 1839
let metersMeasurement = Measurement(value: meters, unit: UnitLength.meters)
let measurementFormatter = MeasurementFormatter()
measurementFormatter.locale = Locale(identifier: "en_US")
let localizedString = measurementFormatter.string(from: metersMeasurement)
The .naturalScale option that answered my previous question doesn't help here. I think this is a limitation of the framework, but I wonder if anyone has a workaround for now.

You just need to convert your UnitLength from meters to feet. You can also create a custom US measurement formatter to display it as needed:
extension Measurement where UnitType == UnitLength {
private static let usFormatted: MeasurementFormatter = {
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.unitOptions = .providedUnit
formatter.numberFormatter.maximumFractionDigits = 0
formatter.unitStyle = .long
return formatter
}()
var usFormatted: String { Measurement.usFormatted.string(from: self) }
}
Playground
let value: Double = 1839
let meters: Measurement<UnitLength> = .init(value: value, unit: .meters)
let feet = meters.converted(to: .feet)
let formatted = feet.usFormatted
print(formatted) // "6,033 feet"\n

I think you are correct there's no way to specify this kind of context. You could do something like:
extension MeasurementFormatter
{
func altitudeString(from measurement: Measurement<UnitLength>) -> String
{
var measurement = measurement
let unitOptions = self.unitOptions
let unitStyle = self.unitStyle
self.unitOptions = .naturalScale
self.unitStyle = .long
var string = self.string(from: measurement)
if string.contains(self.string(from: UnitLength.miles))
{
self.unitStyle = unitStyle
measurement.convert(to: UnitLength.feet)
self.unitOptions = .providedUnit
string = self.string(from: measurement)
}
else if string.contains(self.string(from: UnitLength.kilometers))
{
self.unitStyle = unitStyle
measurement.convert(to: UnitLength.meters)
self.unitOptions = .providedUnit
string = self.string(from: measurement)
}
else
{
self.unitStyle = unitStyle
string = self.string(from: measurement)
}
self.unitOptions = unitOptions
return string
}
}
Maybe there are other culturally specific ways of measuring elevation, but this would seem better than miles and kilometers.

Related

Dynamically using specific variables with custom UITableViewCell

This question will be a follow-up of this previous one.
I'm at the point where the user can create UITableViewCells and enter an amount to a TextField. It is then printed to a label and should update a variable that will be used with another variable for a calculation (basically amount * currentPrice, this is a wallet).
I re-read Apple's doc about TableViews and re-opened the exercises I did when I followed the Swift course but I'm struggling to understand the principle here, so once again, I think I really need someone to explain differently than what I could read so my brain can understand.
How will the cell know what variable to use here:
Entering the amount:
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
let dNumber = formatter.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
walletTableViewCell.amountLabel.text = String(format:"%.8f", eNumber)
UserDefaults.standard.set(walletTableViewCell.amountLabel.text, forKey: "cellAmount")
walletTableViewCell.amountTextField.text = ""
}
Calculations and display:
func updateCellValueLabel(cryptoPrice: String) {
if walletTableViewCell.amountLabel.text == "" {
walletTableViewCell.amountLabel.text = "0.00000000"
}
let formatter1 = NumberFormatter()
formatter1.locale = Locale(identifier: "en_US")
let str = walletTableViewCell.amountLabel.text
let dNumber = formatter1.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
UserDefaults.standard.set(eNumber, forKey: "currentAmount")
guard let cryptoDoublePrice = CryptoInfo.cryptoPriceDic[cryptoPrice] else { return }
WalletViewController.bitcoinAmountValue = cryptoDoublePrice * eNumber
if WalletViewController.currencyCode != "" {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "\(WalletViewController.currencyCode)")
walletTableViewCell.cryptoValueLabel.text = formatter.string(from: NSNumber(value: WalletViewController.amountValue))
}
UserDefaults.standard.set(WalletViewController.walletDoubleValue, forKey: "walletValue")
}
What I can't process here is how can I save the amount entered to the corresponding crypto UserDefaults file, then load this amount to be used with the corresponding variable that has the current price for calculations.
I don't know how to use the correct data for the correct cell.
Should I make this a single function? Should I pass parameters to the function (how do I pass the correct parameters to the corresponding cell with delegate?)? Do I need an array of parameters that will be used at indexPath to the correct cell (but how do I know the order if the user creates cells at will?)?
I'm kind of lost here.

The NSFormatter for Converting English Number to persian or arabic Number in Swift 3

I read the similar questions here and Write this method in my app
let formatter = NumberFormatter()
func convertEngNumToPersianNum(num: String)->String{
let number = NSNumber(value: Int(num)!)
let format = NumberFormatter()
format.locale = Locale(identifier: "fa_IR")
let faNumber = format.string(from: number)
return faNumber!
}
I didn't get Error But I didn't get the result too!
my Number code is this :
let checkNumber = Home2ViewController().customtitle.count
personalCustom.text = ("\(checkNumber)")
I used another Number in another View Controller that works But I want to show this Number in persian or arabic number format not in English format
Try this :
func convertEngNumToPersianNum(num: String)->String{
//let number = NSNumber(value: Int(num)!)
let format = NumberFormatter()
format.locale = Locale(identifier: "fa_IR")
let number = format.number(from: num)
let faNumber = format.string(from: number!)
return faNumber!
}
OR repalce with your line
let number = format.number(from: num)
let faNumber = format.string(from: number!)
You can do something like,
let formatter = NumberFormatter()
formatter.locale = NSLocale.current // you can specify locale that you want
formatter.numberStyle = .decimal
formatter.usesGroupingSeparator = true
let number = formatter.number(from: "١٠.٠٠")
print(number ?? "")
To convert to Arabic while keeping the leading zeros
func convertToArDigits(_ digits: String) -> String {
// We need a CFMutableString and a CFRange:
let cfstr = NSMutableString(string: digits) as CFMutableString
var range = CFRange(location: 0, length: CFStringGetLength(cfstr))
// Do the transliteration (this mutates `cfstr`):
CFStringTransform(cfstr, &range, kCFStringTransformLatinArabic, false)
// Convert result back to a Swift string:
return (cfstr as String)
}
extension String {
public var faToEnDigits : String {
let farsiNumbers = ["٠": "0","١": "1","٢": "2","٣": "3","٤": "4","٥": "5","٦": "6","٧": "7","٨": "8","٩": "9"]
var txt = self
farsiNumbers.map { txt = txt.replacingOccurrences(of: $0, with: $1)}
return txt
}
public var enToFaDigits : String {
let englishNumbers = ["0": "۰","1": "۱","2": "۲","3": "۳","4": "۴","5": "۵","6": "۶","7": "۷","8": "۸","9": "۹"]
var txt = self
englishNumbers.map { txt = txt.replacingOccurrences(of: $0, with: $1)}
return txt
}
}

NSMeasurementFormatter shows Imperial weights but not metric weights?

I have implemented an NSMeasurementFormatter and am having an odd issue. When the app is loaded as en_US, I get all my weights loaded into the text boxes as pounds, brilliant. However when I switch my app to en_GB and load the same data, i get nothing appearing in the text boxes.
When i print the objects the print out states the weights in kg so they are converting correctly, but they arent loading into the actual text boxes to be visible to the user.
Is there any clear cause to this? I have spent a few hours trying to work out why it works for one location but not the other! Appreciate the insight, here is the code:
Here is the initial save of an exercise where it either goes one way ot another depending on the apps setup at this point
if self.localeIdentifier == "en_GB" {
let kgWeight = Measurement(value: Double(self.userExerciseWeight.text!)!, unit: UnitMass.kilograms)
newUserExercise.weight = kgWeight as NSObject?
newUserExercise.initialMetricSystem = self.localeIdentifier
print("SAVED AS \(localeIdentifier) METRIC")
} else if self.localeIdentifier == "en_US" {
let lbsWeight = Measurement(value: Double(self.userExerciseWeight.text!)!, unit: UnitMass.pounds)
newUserExercise.weight = lbsWeight as NSObject?
newUserExercise.initialMetricSystem = self.localeIdentifier
print("SAVED AS \(localeIdentifier) IMPERIAL")
}
Then here is my attempt to later reload this objects weight property
let formatter = MeasurementFormatter()
let exerciseWeight = userExercise.weight as! Measurement<Unit>
let localeIdentifier = UserDefaults.standard.object(forKey: "locale")
let locale = Locale(identifier: localeIdentifier as! String)
formatter.locale = locale
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 2
formatter.numberFormatter = numberFormatter
let finalWeight = formatter.string(from: exerciseWeight)
cell.todaysExerciseWeightLabel.text = finalWeight
}
So when set to US, i get a pounds readout to 2 decimal places, but when i set to GB i get nothing visible, i can just see it did the conversion in the print out of the object, pics below of the console that shows for loading both routines, and shots of the results:
UPDATED BELOW WITH MASSFORMATTER INFO
let localeIdentifier = UserDefaults.standard.object(forKey: "locale") as! Locale
let exerciseWeight = userExercise.weight as! Measurement<Unit>
let formatter = MassFormatter()
formatter.numberFormatter.locale = localeIdentifier
formatter.numberFormatter.maximumFractionDigits = 2
if localeIdentifier.usesMetricSystem {
let kgWeight = exerciseWeight.converted(to: .kilograms)
let finalKgWeight = formatter.string(fromValue: kgWeight.value, unit: .kilogram)
cell.todaysExerciseWeightLabel.text = finalKgWeight
print(formatter.string(fromValue: kgWeight.value, unit: .kilogram))
} else {
let kgWeight = exerciseWeight.converted(to: .pounds)
let finalLbWeight = formatter.string(fromValue: exerciseWeight.value, unit: .pound)
cell.todaysExerciseWeightLabel.text = finalLbWeight
print(formatter.string(fromValue: exerciseWeight.value, unit: .pound))
}
}
}
It all boils down to the following piece of code:
let weight = Measurement(value: 10.0, unit: UnitMass.kilograms)
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_GB")
print(formatter.string(from: weight))
The result is an empty String ("").
This is an obvious bug. Please, report it.
A simple workaround is using the older MassFormatter:
//let locale = Locale(identifier: "en_GB")
let locale = Locale(identifier: "en_US")
let weight = Measurement(value: 10.24, unit: UnitMass.pounds)
let formatter = MassFormatter()
formatter.numberFormatter.locale = locale
formatter.numberFormatter.maximumFractionDigits = 2
if locale.usesMetricSystem {
let kgWeight = weight.converted(to: .kilograms)
print(formatter.string(fromValue: kgWeight.value, unit: .kilogram))
} else {
print(formatter.string(fromValue: weight.value, unit: .pound))
}

How to format a Double into Currency - Swift 3

I'm new to Swift programming and I've been creating a simple tip calculator app in Xcode 8.2, I have my calculations set up within my IBAction below. But when I actually run my app and input an amount to calculate (such as 23.45), it comes up with more than 2 decimal places. How do I format it to .currency in this case?
#IBAction func calculateButtonTapped(_ sender: Any) {
var tipPercentage: Double {
if tipAmountSegmentedControl.selectedSegmentIndex == 0 {
return 0.05
} else if tipAmountSegmentedControl.selectedSegmentIndex == 1 {
return 0.10
} else {
return 0.2
}
}
let billAmount: Double? = Double(userInputTextField.text!)
if let billAmount = billAmount {
let tipAmount = billAmount * tipPercentage
let totalBillAmount = billAmount + tipAmount
tipAmountLabel.text = "Tip Amount: $\(tipAmount)"
totalBillAmountLabel.text = "Total Bill Amount: $\(totalBillAmount)"
}
}
You can use this string initializer if you want to force the currency to $:
String(format: "Tip Amount: $%.02f", tipAmount)
If you want it to be fully dependent on the locale settings of the device, you should use a NumberFormatter. This will take into account the number of decimal places for the currency as well as positioning the currency symbol correctly. E.g. the double value 2.4 will return "2,40 €" for the es_ES locale and "¥ 2" for the jp_JP locale.
let formatter = NumberFormatter()
formatter.locale = Locale.current // Change this to another locale if you want to force a specific locale, otherwise this is redundant as the current locale is the default already
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: tipAmount as NSNumber) {
tipAmountLabel.text = "Tip Amount: \(formattedTipAmount)"
}
How to do it in Swift 4:
let myDouble = 9999.99
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: NSNumber(value: myDouble))!
print(priceString) // Displays $9,999.99 in the US locale
You can to convert like that: this func convert keep for you maximumFractionDigits whenever you want to do
static func df2so(_ price: Double) -> String{
let numberFormatter = NumberFormatter()
numberFormatter.groupingSeparator = ","
numberFormatter.groupingSize = 3
numberFormatter.usesGroupingSeparator = true
numberFormatter.decimalSeparator = "."
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 2
return numberFormatter.string(from: price as NSNumber)!
}
i create it in class Model
then when you call , you can accecpt it another class , like this
print("InitData: result convert string " + Model.df2so(1008977.72))
//InitData: result convert string "1,008,977.72"
you can create an Extension for either string or Int, I would show an example with String
extension String{
func toCurrencyFormat() -> String {
if let intValue = Int(self){
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: "ig_NG")/* Using Nigeria's Naira here or you can use Locale.current to get current locale, please change to your locale, link below to get all locale identifier.*/
numberFormatter.numberStyle = NumberFormatter.Style.currency
return numberFormatter.string(from: NSNumber(value: intValue)) ?? ""
}
return ""
}
}
link to get all locale identifier
The best way to do this is to create an NSNumberFormatter. (NumberFormatter in Swift 3.) You can request currency and it will set up the string to follow the user's localization settings, which is useful.
As an alternative to using a NumberFormatter, If you want to force a US-formatted dollars and cents string you can format it this way:
let amount: Double = 123.45
let amountString = String(format: "$%.02f", amount)
As of Swift 5.5, you can do this with the help of .formatted:
import Foundation
let amount = 12345678.9
print(amount.formatted(.currency(code: "USD")))
// prints: $12,345,678.90
This should support most common currency code, such as "EUR", "GBP", or "CNY".
Similarly, you can append locale to .currency:
print(amount.formatted(
.currency(code:"EUR").locale(Locale(identifier: "fr-FR"))
))
// prints: 12 345 678,90 €
In addition to the NumberFormatter or String(format:) discussed by others, you might want to consider using Decimal or NSDecimalNumber and control the rounding yourself, thereby avoid floating point issues. If you're doing a simple tip calculator, that probably isn't necessary. But if you're doing something like adding up the tips at the end of the day, if you don't round the numbers and/or do your math using decimal numbers, you can introduce errors.
So, go ahead and configure your formatter:
let formatter: NumberFormatter = {
let _formatter = NumberFormatter()
_formatter.numberStyle = .decimal
_formatter.minimumFractionDigits = 2
_formatter.maximumFractionDigits = 2
_formatter.generatesDecimalNumbers = true
return _formatter
}()
and then, use decimal numbers:
let string = "2.03"
let tipRate = Decimal(sign: .plus, exponent: -3, significand: 125) // 12.5%
guard let billAmount = formatter.number(from: string) as? Decimal else { return }
let tip = (billAmount * tipRate).rounded(2)
guard let output = formatter.string(from: tip as NSDecimalNumber) else { return }
print("\(output)")
Where
extension Decimal {
/// Round `Decimal` number to certain number of decimal places.
///
/// - Parameters:
/// - scale: How many decimal places.
/// - roundingMode: How should number be rounded. Defaults to `.plain`.
/// - Returns: The new rounded number.
func rounded(_ scale: Int, roundingMode: RoundingMode = .plain) -> Decimal {
var value = self
var result: Decimal = 0
NSDecimalRound(&result, &value, scale, roundingMode)
return result
}
}
Obviously, you can replace all the above "2 decimal place" references with whatever number is appropriate for the currency you are using (or possibly use a variable for the number of decimal places).
extension String{
func convertDoubleToCurrency() -> String{
let amount1 = Double(self)
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
numberFormatter.locale = Locale(identifier: "en_US")
return numberFormatter.string(from: NSNumber(value: amount1!))!
}
}
In 2022 using Swift 5.5, I created extensions that convert Float or Double into a currency using your device's locale or the locale you pass as an argument. You can check it out here https://github.com/ahenqs/SwiftExtensions/blob/main/Currency.playground/Contents.swift
import UIKit
extension NSNumber {
/// Converts an NSNumber into a formatted currency string, device's current Locale.
var currency: String {
return self.currency(for: Locale.current)
}
/// Converts an NSNumber into a formatted currency string, using Locale as a parameter.
func currency(for locale: Locale) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.usesGroupingSeparator = locale.groupingSeparator != nil
numberFormatter.numberStyle = .currency
numberFormatter.locale = locale
return numberFormatter.string(from: self)!
}
}
extension Double {
/// Converts a Double into a formatted currency string, device's current Locale.
var currency: String {
return NSNumber(value: self).currency(for: Locale.current)
}
/// Converts a Double into a formatted currency string, using Locale as a parameter.
func currency(for locale: Locale) -> String {
return NSNumber(value: self).currency(for: locale)
}
}
extension Float {
/// Converts a Float into a formatted currency string, device's current Locale.
var currency: String {
return NSNumber(value: self).currency(for: Locale.current)
}
/// Converts a Float into a formatted currency string, using Locale as a parameter.
func currency(for locale: Locale) -> String {
return NSNumber(value: self).currency(for: locale)
}
}
let amount = 3927.75 // Can be either Double or Float, since we have both extensions.
let usLocale = Locale(identifier: "en-US") // US
let brLocale = Locale(identifier: "pt-BR") // Brazil
let frLocale = Locale(identifier: "fr-FR") // France
print("\(Locale.current.identifier) -> " + amount.currency) // default current device's Locale.
print("\(usLocale.identifier) -> " + amount.currency(for: usLocale))
print("\(brLocale.identifier) -> " + amount.currency(for: brLocale))
print("\(frLocale.identifier) -> " + amount.currency(for: frLocale))
// will print something like this:
// en_US -> $3,927.75
// en-US -> $3,927.75
// pt-BR -> R$ 3.927,75
// fr-FR -> 3 927,75 €
I hope it helps, happy coding!
extension Float {
var localeCurrency: String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
return formatter.string(from: self as NSNumber)!
}
}
amount = 200.02
print("Amount Saved Value ",String(format:"%.2f", amountSaving. localeCurrency))
For me Its return 0.00!
Looks to me Extenstion Perfect when accessing it return 0.00! Why?
Here's an easy way I've been going about it.
extension String {
func toCurrency(Amount: NSNumber) -> String {
var currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.current
return currencyFormatter.string(from: Amount)!
}
}
Being used as follows
let amountToCurrency = NSNumber(99.99)
String().toCurrency(Amount: amountToCurrency)
Here's how:
let currentLocale = Locale.current
let currencySymbol = currentLocale.currencySymbol
let outputString = "\(currencySymbol)\(String(format: "%.2f", totalBillAmount))"
1st line: You're getting the current locale
2nd line: You're getting the currencySymbol for that locale. ($, £, etc)
3rd line: Using the format initializer to truncate your Double to 2 decimal places.

Convert Double to Scientific Notation in swift

I am trying to convert a given double into scientific notation, and running into some problems. I cant seem to find much documentation on how to do it either. Currently I am using:
var val = 500
var numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.ScientificStyle
let number = numberFormatter.numberFromString("\(val)")
println(number as Double?)
// Prints optional(500) instead of optional(5e+2)
What am I doing wrong?
You can set NumberFormatter properties positiveFormat and exponent Symbol to format your string as you want as follow:
let val = 500
let formatter = NumberFormatter()
formatter.numberStyle = .scientific
formatter.positiveFormat = "0.###E+0"
formatter.exponentSymbol = "e"
if let scientificFormatted = formatter.string(for: val) {
print(scientificFormatted) // "5e+2"
}
update: Xcode 9 • Swift 4
You can also create an extension to get a scientific formatted description from Numeric types as follow:
extension Formatter {
static let scientific: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .scientific
formatter.positiveFormat = "0.###E+0"
formatter.exponentSymbol = "e"
return formatter
}()
}
extension Numeric {
var scientificFormatted: String {
return Formatter.scientific.string(for: self) ?? ""
}
}
print(500.scientificFormatted) // "5e+2"
The issue is that you are printing the number... not the formatted number. You are calling numberForString instead of stringForNumber
var val = 500
var numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.ScientificStyle
let numberString = numberFormatter.stringFromNumber(val)
println(numberString)
Slight modification to the answer by leo-dabus to Xcode 9 Swift 4:
extension Double {
struct Number {
static var formatter = NumberFormatter()
}
var scientificStyle: String {
Number.formatter.numberStyle = .scientific
Number.formatter.positiveFormat = "0.###E+0"
Number.formatter.exponentSymbol = "e"
let number = NSNumber(value: self)
return Number.formatter.string(from :number) ?? description
}
}

Resources