Migrating ObjC NSNumber code to Swift and having problems with displaying numbers. - ios

I am migrating my ObjC app to Swift and have run into a peculiar issue.
My app reads a JSON file and builds a model from it. The numbers read from JSON used to be assigned to NSNumber. Some numbers were whole (eg.60) other had a decimal component (e.g. 60.23).
When I displayed these numbers in my app, 60 would be 60 (not 60.0) and decimals would display appropriately.
Now I am writing Swift code and assigning the numbers read from JSON to Double. Even if I use let x=String(myDouble) the number 60 will always come out 60.0
I don't want this...but I can't just format it %f because I never know if the number will be whole or have a decimal component.
Do I really need to check if it is a whole and then format it using it %f?
Am I missing something in Swift? The ObjC code behaved as I wanted by now Swift seems to be giving me decimals for whole numbers.

You could use NSNumberFormatter.
let number1 : Double = 60
let number2 = 60.23
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .DecimalStyle
let number1String = numberFormatter.stringFromNumber(number1) // "60"
let number2String = numberFormatter.stringFromNumber(number2) // "60.23"

FYI, here is the code in PlayGround:
let number = 60.23
String(number)
let double:Double = 60
//First way
String(NSNumberFormatter().stringFromNumber(double)!)
//Second way
let doubleString = String(double)
if doubleString.containsString(".0") {
let length = doubleString.characters.count
let index = doubleString.characters.indexOf(".")
doubleString.substringToIndex(index!)
}

Related

String formatting. Swift / iOS [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 months ago.
Improve this question
I ran into such a problem: from the server I receive such a figure in the string format "20760.326586753041" (example), but I want to change it so that the user's screen has such a figure 20,761.93.
How can I format it?
Tried to do like this:
func setup(coin: Coin) {
self.nameCoin.text = coin.name
self.symbolCoin.text = coin.symbol
self.priceCoin.text = String(format: "%.2f", coin.priceUsd)
}
Show 0.0 on screen
You should use a NumberFormatter
import Foundation
let coinFormatter : NumberFormatter = {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en-US")
return formatter
}()
let stringFromServer = "20760.326586753041"
if let value = coinFormatter.number(from: stringFromServer),
let reformattedString = coinFormatter.string(for: value) {
print(reformattedString)
}
I create a number formatter called coinFormatter that format numbers into decimals that have at most 2 decimal places. The code below that shows how you might use such a number formatter to convert the string from the server to a number, then the number back to a string with the expected format.
It also looks like you might be trying to format the number as a currency value. There are mechanisms in NumberFormatter for properly formatting currency values that you should look into as well.
P.S. As you will see in the comments below, NumberFormatter takes into account many complexities like the Locale and common radix marks used, how you want negative numbers represented, or whether the currency symbol should be written before or after the number when representing money. Please take the time to learn more about NumberFormatter and the power it offers you.

How to convert number inputted in any locale inside a UITextField into English number in iOS Swift

I am trying to get some decimal number from the user inside a UITextfield in iOS Swift. Now the user can input number in his or her local number format as per the locale Settings in iOS. I want to convert this number which is in the user's mother tongue into English number. I searched a lot in this site (stackoverflow.com) and the majority of answers are for conversion from one locale (Chinese, or Arabic or Persian) into English but I want to convert number inputted into any locale format into English. How can I do this? So in nutshell, my question is whether the number being inputted in UITextField is in Hindi, Arabic, Persian, Chinese or whatsoever format as per the locale, I want to convert it into English Number format.
you can use NumberFormatter for that.
check below example:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let localNumberInStr = "૨૩"
guard let str = numberFormatter.number(from: localNumberInStr) else {return}
print(str) //"23"
When you check the devices locale you know which locale the user is using.
let locale = Locale.current
Just to improve upon Dharmesh answer, here is the answer wrapped in a helper method for use throughout the code. Obviously, it assumes that while getting user input via UITextField one has considered the number set in the user's locale settings.
func convertLocaleNumberIntoEnglish(localeNumberString: String?) -> String? {
guard let ulocaleNumberString = localeNumberString else {return nil}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let localNumberInStr = ulocaleNumberString
guard let number = numberFormatter.number(from: localNumberInStr) else {return nil}
let str = String(format:"%f", number.doubleValue)
return str
}

Formatting localized numbers with unit symbols

Situation
I want to format a Double 23.54435678 into a String like 23.54 fps respecting the user's locale.
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
let formatted = formatter.string(from: fps as NSNumber)! + " fps"
For the localized number formatting I use DateFormatter.
Question
How should I handle the unit part? Is it valid to just append the unit to the formatted number? Is the placement of the symbol not locale dependent? How do I handle that?
Cocoa has no built-in support for the unit "frames per second", so you will have to provide the suffix yourself, e.g. using Xcode's localization system.
You still need to format the numeric value with NumberFormatter for the current locale and then insert the resulting number string into the localized format string:
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
let numberString = formatter.string(from: fps)
let formatString = NSLocalizedString("%# fps", comment: "") // provide localizations via .strings files
let fpsString = String(format: formatString, arguments: numberString)
If unit placement is locale-dependent (you will have to this find out yourself for the target locales of your app), you have to deal with this manually as well. You can leverage the localization system here by providing localizations with an adequately positioned placeholder for the numeric value, e.g. %# fps for English and x %# yz for... well, Fantasy Language.

Format speed in Swift

I want to be able to format and print speed in localised form. I found a few pods that can do that but I'm looking for embedded solution (if possible).
Currently I do next:
let unit = HKUnit.meterUnit(with: .kilo).unitDivided(by: .hour())
let output = HKQuantity(unit: unit, doubleValue: 12.5).description
But in this case I cannot tune anything like use h instead of hr.
As said #Allan we have to use MeasurementFormatter. Check out the ready to use solution below.
import UIKit
import HealthKit
let value = NSMeasurement(doubleValue: 12.5, unit: UnitSpeed.milesPerHour)
let formatter = MeasurementFormatter()
formatter.numberFormatter.maximumFractionDigits = 2
formatter.string(from: value as Measurement<Unit>) // prints 12.5 mph
The description method of HKQuantity does not return a localized value and it is meant for debugging, not for display. Convert the HKQuantity instance to an NSMeasurement and use NSMeasurementFormatter (documentation here) to localize the value for display.

In Swift, how can I get an NSDate from a dispatch_time_t?

"Walltime" is a little-known time format used by Grand Central Dispatch. Apple talks about it here:
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/
There are some things it's really handy for, though, but it's a sticky wicket. It's hard to make it play nice with other time formats, which is what my question's about.
I can make a walltime by turning an NSDate into a timespec, and then using with dispatch_walltime:
let now = NSDate().timeIntervalSince1970
let nowWholeSecsFloor = floor(now)
let nowNanosOnly = now - nowWholeSecsFloor
let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
tv_nsec: Int(nowNanosFloor))
let wallTime = dispatch_walltime(& thisStruct, 0)
But lord love a duck, I can't figure out how to get it back into an NSDate. Here's my try:
public func toNSDate(wallTime: dispatch_time_t)->NSDate {
let wallTimeAsSeconds = Double(wallTime) / Double(NSEC_PER_SEC)
let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
return date
}
The resulting NSDate is not just off, but somewhat hilariously off, like five hundred years or something. As Martin R pointed out, the problem is that dispatch_time_t is an opaque value, with an undocumented representation of time.
Does anyone know how to do this?
EDIT: if the process of creating the walltime is confusing, this is basically what's going on:
NSDate defines time with a Double, and everything after the decimal point is the nanoseconds. dispatch_time, which can create a walltime, defines time with UInt64, so you have to convert between Double and UInt64 to use it. To do that conversion you need to use a timespec, which takes seconds and nanoseconds as separate arguments, each of which must be Int.
A whole lotta convertin' going on!
The real answer is: you can't.
In the "time.h" header file it is stated:
/*!
* #typedef dispatch_time_t
*
* #abstract
* A somewhat abstract representation of time; where zero means "now" and
* DISPATCH_TIME_FOREVER means "infinity" and every value in between is an
* opaque encoding.
*/
typedef uint64_t dispatch_time_t;
So dispatch_time_t uses an undocumented "abstract" representation of time, which
may even change between releases.
That being said, let's have some fun and try to figure out what
a dispatch_time_t really is. So we have a look at "time.c", which contains the implementation of
dispatch_walltime():
dispatch_time_t
dispatch_walltime(const struct timespec *inval, int64_t delta)
{
int64_t nsec;
if (inval) {
nsec = inval->tv_sec * 1000000000ll + inval->tv_nsec;
} else {
nsec = (int64_t)_dispatch_get_nanoseconds();
}
nsec += delta;
if (nsec <= 1) {
// -1 is special == DISPATCH_TIME_FOREVER == forever
return delta >= 0 ? DISPATCH_TIME_FOREVER : (dispatch_time_t)-2ll;
}
return (dispatch_time_t)-nsec;
}
The interesting part is the last line: it takes the negative value of the
nanoseconds, and this value is cast back to an (unsigned) dispatch_time_t. There are also some special cases.
Therefore, to reverse the conversion, we have to negate the
dispatch_time_t and take that as nanoseconds:
public func toNSDate(wallTime: dispatch_time_t)->NSDate {
// Tricky part HERE:
let nanoSeconds = -Int64(bitPattern: wallTime)
// Remaining part as in your question:
let wallTimeAsSeconds = Double(nanoSeconds) / Double(NSEC_PER_SEC)
let date = NSDate(timeIntervalSince1970: wallTimeAsSeconds)
return date
}
And indeed, this converts the walltime correctly back to the original
NSDate, at least when I test it in an OS X application.
But again: don't do it! You would rely on an undocumented representation which could change between OS releases. There may also
be special cases that are not considered in the above code.
Also the representation in the iOS runtime could be different, I did
not try that.
You have been warned!

Resources