Singleton and NSNumberFormatter in Swift - ios

Currently, I have the following code in one of my methods:
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.currencyGroupingSeparator?
formatter.minimumFractionDigits = 2
Because I have to repeat these in various functions in different view controllers, how do I create a singleton in Swift to call for the NSNumberFormatter and avoid duplicates?
I assume that I have to create a new Swift file, but unsure of how to construct the class?

update: Xcode 8.2.1 • Swift 3.0.2
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) ?? ""
}
}
100.954345.formatted // 100.95

Ok, what I'd do is make a property that is lazily instantiated. That way you will have no duplicates, and it won't be created and take up memory until you need it.
lazy var numberFormatter = [NSNumberFormatter] = {
var _numberFormatter = [NSNumberFormatter]()
_numberFormatter.numberStyle = .DecimalStyle
_numberFormatter.currencyGroupingSeparator?
_numberFormatter.minimumFractionDigits = 2
return _numberFormatter
}()

NSNumberFormatter isn't that expensive (as compared to NSDateFormatter) but if you want to have a class method which vends a once only instantiated object
class var prettyFormatter:NSNumberFormatter {
struct SingletonNumberFormatter {
static var instance:NSNumberFormatter?
}
if SingletonNumberFormatter.instance == nil {
SingletonNumberFormatter.instance = NSNumberFormatter()
SingletonNumberFormatter.instance.numberStyle = .DecimalStyle
SingletonNumberFormatter.instance.currencyGroupingSeparator?
SingletonNumberFormatter.instance.minimumFractionDigits = 2
}
return SingletonNumberFormatter.instance!
}

Another Singleton solution:
class Formatters{
static let twoFractionDigits: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
static func formatNumber(fromNumber: NSNumber) -> String{
return twoFractionDigits.string(from: fromNumber) ?? ""
}
}
Usage:
Formatters.formatNumber(fromNumber: 100)
When you have a variable with types of Int, CGFloat etc, use:
Formatters.formatNumber(fromNumber: 100 as NSNumber)

Related

Swift, converting a number to string, number gets rounded down

I'm having a bit of issue with my code...right now, I am passing a string containing a bunch of numbers, to get converted to a number, comma separators added, then converted back to a string for output. When I add a decimal to my string and pass it in, a number like 996.3658 get truncated to 996.366...
"currentNumber" is my input value, "textOutputToScreen" is my output...
func formatNumber() {
let charset = CharacterSet(charactersIn: ".")
if let _ = currentNumber.rangeOfCharacter(from: charset) {
if let number = Float(currentNumber) {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
guard let formattedNumber = numberFormatter.string(from: NSNumber(value: number)) else { return }
textOutputToScreen = String(formattedNumber)
}
}
else {
if let number = Int(currentNumber) {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
guard let formattedNumber = numberFormatter.string(from: NSNumber(value: number)) else { return }
textOutputToScreen = String(formattedNumber)
}
}
}
Thank you in advance for your help!
The issue there is that you have to set your NumberFormatter minimumFractionDigits to 4. Btw there is no need to initialize a NSNumber object. You can use Formatters string(for: Any) method and pass your Float. Btw I would use a Double (64-bit) instead of a Float (32-bit) and there is no need to initialize a new string g from your formattedNumber object. It is already a String.
Another thing is that you don't need to know the location of the period you can simply use contains instead of rangeOfCharacter method. Your code should look something like this:
extension Formatter {
static let number: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
}
func formatNumber(from string: String) -> String? {
if string.contains(".") {
guard let value = Double(string) else { return nil }
Formatter.number.minimumFractionDigits = 4
return Formatter.number.string(for: value)
} else {
guard let value = Int(string) else { return nil }
Formatter.number.minimumFractionDigits = 0
return Formatter.number.string(for: value)
}
}
let label = UILabel()
let currentNumber = "996.3658"
label.text = formatNumber(from: currentNumber) // "996.3658\n"
If you would like to assign the result to your var instead of a label
if let formatted = formatNumber(from: currentNumber) {
textOutputToScreen = formatted
}

setting class properties during initialization swift

I'm trying to create two NSNumberFormater objects -- one with maximumFractionDigits of 0 and one with maximumFractiongDigits of 6. I would like to do this during initialization so I could use these objects repeatedly. What I have right now is:
private let formatter = NSNumberFormatter()
private var niceAccumulator : String {
if accumulator % 1 == 0 {
formatter.maximumFractionDigits = 0
return String(formatter.stringFromNumber(accumulator)!)
}
else {
formatter.maximumFractionDigits = 6
return String(formatter.stringFromNumber(accumulator)!)
}
}
What I would like to do is something along the lines of:
private let intFormatter = NSNumberFormatter(maximumFractionDigits: 0)
private let doubleFormatter = NSNumberFormatter(maximumFractionDigits: 6)
private var niceAccumulator : String {
if accumulator % 1 == 0 {
return String(intFormatter.stringFromNumber(accumulator)!)
}
else {
return String(doubleFormatter.stringFromNumber(accumulator)!)
}
}
I'm just learning to code again so I really appreciate the help. Thanks everyone!
You have two options. As #TofuBeer indicated in his answer, you can use an initializer:
private let intFormatter: NSNumberFormatter
private let doubleFormatter: NSNumberFormatter
init() {
intFormatter = NSNumberFormatter()
intFormatter.maximumFractionDigits = 0
doubleFormatter = NSNumberFormatter()
doubleFormatter.maximumFractionDigits = 6
// Call to an appropriate super.init, assuming you are subclassing something
}
You can also initialize your property with a closure, if you prefer not to use an initializer:
private let intFormatter: NSNumberFormatter = {
let formatter = NSNumberFormatter()
formatter.maximumFractionDigits = 0
return formatter
}()
private let doubleFormatter: NSNumberFormatter = {
let formatter = NSNumberFormatter()
formatter.maximumFractionDigits = 6
return formatter
}()
If you find you need to use this construct a lot in your project, it is worth pointing out that you can define your own convenience initializer:
extension NSNumberFormatter {
convenience init(maximumFractionDigits: Int) {
self.init()
self.maximumFractionDigits = maximumFractionDigits
}
}
Then you can do:
private let intFormatter = NSNumberFormatter(maximumFractionDigits: 0)
private let doubleFormatter = NSNumberFormatter(maximumFractionDigits: 6)
Use an initializer
private let intFormatter = NSNumberFormatter()
private let doubleFormatter = NSNumberFormatter()
init()
{
intFormatter.maximumFractionDigits = 0
doubleFormatter.maximumFractionDigits = 6
}

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
}
}

How can I format currency depending on decimal value?

I am using NSDecimalNumber to format currency and want the following inputs and outputs:
9.99 --> 9.99
10 --> 10
10.00 --> 10
9.90 --> 9.90
9.9 --> 9.90
0 --> 0
0.01 --> 0.01
20 --> 20
10.01 --> 10.01
How can I do this in Swift.
EDIT: Essentially if there are cents (i.e. cents > 0) then display the cents. Otherwise, don't.
Your rule is "Display two fractional digits if either is non-zero; otherwise, display no fractional digits and no decimal point”. I would do it in the most straightforward way:
let number = NSDecimalNumber(string: "12345.00")
let formatter = NSNumberFormatter()
formatter.positiveFormat = "0.00"
let formattedString = formatter.stringFromNumber(number)!
.stringByReplacingOccurrencesOfString(".00", withString: "")
You can use NSNumberFormatter's currency formatting for this. However, there doesn't seem to be a built-in way to do rounding the way you want. Here's a workaround:
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
func numToCurrency (num: Double) -> String {
if floor(num) == num {
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 0
}
else {
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
}
return formatter.stringFromNumber(num)!
}
numToCurrency(9) // "$9"
numToCurrency(9.9) // "$9.90"
Check the NSNumberFormatter class reference for further configuration options (you might need to set a locale for this formatter to automatically use the correct international currency sign for the current user).
(Answering here, as a closed question was re-directed to this one...)
Perhaps the most straightforward route, particularly since this is tagged "Swift", is to determine if it's a whole number or not:
if value.truncatingRemainder(dividingBy: 1) == 0 {
// it's a whole number,
// so format WITHOUT decimal places, e.g. $12
} else {
// it's a fraction,
// so format WITH decimal places, e.g. $12.25
}
the added benefit is avoiding issues with locales and currency formats... no search/replace of ".00" when you're in Germany, for example, where the format is ",00"
edit/update: Xcode 8.3 • Swift 3.1
extension Formatter {
static let noFractionDigits: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 0
formatter.minimumIntegerDigits = 1
return formatter
}()
static let twoFractionDigits: NumberFormatter = {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
formatter.minimumIntegerDigits = 1
return formatter
}()
}
extension FloatingPoint {
var customDescription: String {
return rounded(.down) == self ?
Formatter.noFractionDigits.string(for: self) ?? "" :
Formatter.twoFractionDigits.string(for: self) ?? ""
}
}
extension String {
var double: Double { return Double(self) ?? 0 }
}
let array = ["9.99","10","10.00","9.90","9.9"]
let results = array.map { $0.double.customDescription }
results // ["9.99", "10", "10", "9.90", "9.90"]
Here's how to create a custom formatter class to handle this for you:
import Foundation
class CustomFormatter: NSNumberFormatter {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init() {
super.init()
self.locale = NSLocale.currentLocale()
self.numberStyle = .DecimalStyle
}
func isIntegerNumber(number:NSNumber) -> Bool {
var value: NSDecimal = number.decimalValue
if NSDecimalIsNotANumber(&value) { return false }
var rounded = NSDecimal()
NSDecimalRound(&rounded, &value, 0, NSRoundingMode.RoundPlain)
return NSDecimalCompare(&rounded, &value) == NSComparisonResult.OrderedSame
}
override func stringFromNumber(number: NSNumber) -> String? {
if isIntegerNumber(number) {
self.minimumFractionDigits = 0
self.maximumFractionDigits = 0
return super.stringFromNumber(number)
}
else {
self.minimumFractionDigits = 2
self.maximumFractionDigits = 2
return super.stringFromNumber(number)
}
}
}
let formatter = CustomFormatter()
formatter.stringFromNumber(NSDecimalNumber(double: 5.00)) // -> "5"
formatter.stringFromNumber(NSDecimalNumber(double: 5.01)) // -> "5.01"
formatter.stringFromNumber(NSDecimalNumber(double: 5.10)) // -> "5.10"
Thanks to this post for the proper way to test if a NSDecimal is an integer.
I think it's best to let the currencyStyle determine the maximumFractionDigits. Just set the minimumFractionDigits to 0 where desired. The code is slightly shorter, but as a bonus if you set the locale, this way will allow for languages that don't have 2 decimal places.
Using NSNumberFormatter gives you the benefit of currency symbols, decimal places and comma’s, all in the perfect places for the different locale’s.
extension NSNumber {
func currencyString() -> String? {
let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
if self.isEqualToNumber(self.integerValue) {
formatter.minimumFractionDigits = 0
}
return formatter.stringFromNumber(self)
}
}
let inputArray: [NSDecimalNumber] = [9.99, 10, 10.00, 9.90, 0, 0.01, 20, 10.01, 0.5, 0.055, 5.0]
let outputArray: [String] = inputArray.map({return $0.currencyString() ?? "nil"})
print(outputArray)
["$9.99", "$10", "$10", "$9.90", "$0", "$0.01", "$20", "$10.01", "$0.50", "$0.06", "$5"]
Adding a locale to a NSNumberFormatter looks like this(ex. from an SKProduct object):
formatter.locale = product!.priceLocale
For an OSX app you need to add:
formatter.formatterBehavior = .Behavior10_4

Format Int64 with thousand separators

I have used below code successfully to format Int with thousand separators. However my current project required Int64 to be formatted the same way and it throws error 'Int64' is not convertible to 'NSNumber'
var numberFormatter: NSNumberFormatter {
let formattedNumber = NSNumberFormatter()
formattedNumber.numberStyle = .DecimalStyle
formattedNumber.maximumFractionDigits = 0
return formattedNumber
}
You mean when you call numberFormatter.stringFromNumber(12345678) after the above code, like this?
let i64: Int64 = 1234567890
numberFormatter.stringFromNumber(i64)
Doesn’t look like Swift will cast from an Int64 to an NSNumber:
let i = 1234567890
let n = i as NSNumber // OK
numberFormatter.stringFromNumber(i) // Also OK
// Compiler error: 'Int64' is not convertible to 'NSNumber'
let n64 = i64 as NSNumber
// so the implicit conversion will also fail:
numberFormatter.stringFromNumber(i64)
This is a bit confounding, since Swift Ints are themselves usually the same size as Int64s.
You can work around it by constructing an NSNumber by hand:
let n64 = NSNumber(longLong: i64)
BTW beware that var trick: it’s nice that it encapsulates all the relevant code for creating numberFormatter, but that code will run afresh every time you use it. As an alternative you could do this:
let numberFormatter: NSNumberFormatter = {
let formattedNumber = NSNumberFormatter()
formattedNumber.numberStyle = .DecimalStyle
formattedNumber.maximumFractionDigits = 0
return formattedNumber
}()
If it’s a property in a struct/class, you could also make it a lazy var which has the added benefit of only being running if the variable is used, like your var, but only once.
struct Thing {
lazy var numberFormatter: NSNumberFormatter = {
println("blah")
let formattedNumber = NSNumberFormatter()
formattedNumber.numberStyle = .DecimalStyle
formattedNumber.maximumFractionDigits = 0
return formattedNumber
}()
}
extension Formatter {
static let decimalNumber: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
}
extension Numeric {
var formatted: String { Formatter.decimalNumber.string(for: self) ?? "" }
}
let x: Int64 = 1000000
x.formatted // "1,000,000"

Resources