iOS Swift: Formatting a custom Currency Number - ios

Formatting large numbers with comma separator.
Solved (The code is updated and fully working)
Evening, I have a typealias Currency from Double.
I want to print it with the comma between the thousands.
this is what I did:
import Foundation
typealias Currency = Double
extension Currency {
var credit: Double { return self }
var usd: Double { return self * 0.62 }
func description() -> String {
let price = self as NSNumber
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter.string(from: price)!
}
}
let price: Currency = 1000000000
print(price.description)
/* It doesn't work, I want something like 1000,000,000.0 */
But it doesn't work. What is wrong? 🤔

description is a property that comes baked into Foundation via the CustomStringConvertible protocol which states the description variable as:
A textual representation of the value.
You're looking to call your description() method. Add parentheses and you'll get your desired result:
price.description()

You have defined a function description and I think it'll work as you expected if you call price.description(). It looks like you intended to override the default behavior of a CustomStringConvertable type but that uses a var description: String { get } property, not a function.

Related

Swift converting string to double with numberformatter returns nil

I am trying to convert the value of a text field, which the user enters. As users are able to enter decimal values in european number format too, I use numberformatter for that. This was what I was trying:
let newprice = MaximumPriceLabel.text as! String
print(newprice) -> result: Optional("10,00")
print(formatter.number(from: newprice as String)) -> result: nil
print(Double(formatter.number(from: ("10,11"))!)) -> result: 10.11 -> that is what I want
So there is a value stored in the variable newprice, but when formatting, it returns nil. When I test it with a manual value, it works. Is there anything I am doing wrong? Formatter.local is set to Locale.current.
Update: The problem seems to be that the MaximumPriceLabel.text contains not only the value I need, but really the text "Optional("10,00") - which fails when converting to a number. The value is filled like this:
self.MaximumPriceLabel.text = String(describing: formatter.string(from: NSNumber(value: desprice)))
and when I do a print afterwards, I receive "Optional("Optional(\"10,00\")")" -> even when I clear the variable MaximumPriceLabel.text first.
You need to learn how to deal with optionals.
Your first issue is how you set the label's text. Please note that NumberFormatter string(from:) returns an optional String. And never, never use String(describing:) to display information to a user. That should only be used for debugging.
Start by changing:
self.MaximumPriceLabel.text = String(describing: formatter.string(from: NSNumber(value: desprice)))
to:
if let text = formatter.string(from: NSNumber(value: desprice)) {
MaximumPriceLabel.text = text
}
That will fix the issue of the label's text having the text Optional(...).
Then your code for converting the label's text back to a string needs to be updated as:
if let newprice = MaximumPriceLabel.text {
if let price = formatter.number(from: newprice) {
print("The price is \(price)")
} else {
print("Invalid number: \(newprice)")
}
}

Convert Objective-C enum to Swift String

I'm trying to convert iOS error codes to String in Swift 2 (XCode 7.2). But converting to String returns the type name instead of the value name for system enums.
This is what I'm trying:
import CoreLocation
import EventKit
let clError = CLError.LocationUnknown
let clErrorString = "\(clError)"
// EXPECTED: 'LocationUnknown'. OBTAINED: 'CLError'
let ekError = EKErrorCode.CalendarIsImmutable
let ekErrorString = "\(ekError)"
// EXPECTED: 'CalendarIsImmutable'. OBTAINED: 'EKErrorCode'
But with enums declared in Swift, this works as expected:
enum _EKErrorCode : Int {
case CalendarIsImmutable
}
let _ekError = _EKErrorCode.CalendarIsImmutable
let _ekErrorString = "\(_ekError)"
// EXPECTED: 'CalendarIsImmutable'. OBTAINED: 'CalendarIsImmutable'
I'm trying to avoid a Switch-Case with all posible enum values, or extending system types adding a custom description.
This can be achieved in the following way without manually checking the cases by using the ClError.Code extension
extension CLError.Code {
func getErrorDescription() -> String {
return String(describing: self)
}
}
Usage:
let clError = CLError.locationUnknown
print (clError.getErrorDescription())

NSNumber? to a value of type String? in CoreData

So I can't figure this out, am I supposed to change the '.text' to something else or do I have to go about converting the string into a double?
Here is the code
if item != nil {
// the errors I keep getting for each one is
unitCost.text = item?.unitCost //cannot assign to a value 'NSNumber?' to a value of type 'String?'
total.text = item?.total //cannot assign to a value 'NSNumber?' to a value of type 'String?'
date.text = item?.date //cannot assign to a value 'NSDate?' to a value of type 'String?'
}
You are trying to assign an invalid type to the text property. The text property is of type String? as stated by the compiler error. You are trying to assign an NSNumber or NSDate. The expected type is a String or nil and so you must ensure that you provide only those two possibilities. As a result, you need to convert your numbers and dates into strings.
In Swift, there is no need to use format specifiers. Instead, best practice is to use string interpolation for simple types like numbers:
unitCost.text = "\(item?.unitCost!)"
total.text = "\(item?.total!)"
For dates, you can use NSDateFormatter to produce a human-friendly date in a desired format:
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
date.text = "\(formatter.stringFromDate(date))"
While we're at it, why not use optional binding instead of nil comparison:
if let item = item {
// Set your properties here
}
Try this:
unitCost.text = String(format: "%d", item?.unitCost?.integerValue)
You can add an extension for Double/NSNumber/NSDate
extension Double {
func toString() -> String {
return NSNumberFormatter().stringFromNumber(self) ?? ""
}
}
extension NSNumber {
func toString() -> String {
return NSNumberFormatter().stringFromNumber(self) ?? ""
}
}
var doubleValue: Double?
doubleValue?.toString()
if doubleValue is not set it returns empty string. You can make toString() return String? too.. depends on what you need
also, item != nil check is not required in your code as it is optional.
#dbart "\(item?.unitCost)" displays an optional value as String, like Optional(5) rather than 5, we need to unwrap the value
check this code:
if let requiredItem = item {
unitCost.text = requiredItem.unitCost ? "\(requiredItem.unitCost)" : ""
total.text = requiredItem.total ? "\(requiredItem.total)" : ""
date.text = requiredItem.date ? "\(requiredItem.date)" : ""
}

Create a .toString() extension for all numbers in Swift

I'm trying to create a .toString method extension for numbers using Swift just for fun. It works if I specify Int, or Double, etc. It doesn't work right out of the box with NSNumber since it is an object in itself, right? Is there a way to 'catch all' ints floats doubles etc? Some base number class of some sort?
I see this other answer, but I don't want to create a custom type/protocol, just plug this on any 'number'.
Generic type constraint for numerical type only
extension Int {
func toString () -> String {
return String (self)
}
}
You don't need to reinvent the wheel. You can use Swift native description property for Double, Int, and NSNumbers:
Int(1).description // "1"
Double(2.0).description // "2.0"
NSNumber(double: 3.0).doubleValue.description // "3.0"
let x : Int = 33
let stringValue = "\(x)"
or
let stringValue = String(x)
or
let stringValue = Int(x).description

Check if string is already a currency string?

I would like to create a function that looks at a string, and if it's a decimal string, returns it as a currency-formatted string. The function below does that, however if I pass in a string that is already formatted, it will fail of course (it expects to see a string like '25' or '25.55' but not '$15.25'
Is there a way to modify my function below to add another if condition that says "if you've already been formatted as a currency string, or your string is not in the right format, return X" (maybe X will be 0, or maybe it will be self (the same string) i'm not sure yet).
func toCurrencyStringFromDecimalString() -> String
{
var numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
if (self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()).utf16Count == 0)
{
//If whitespace is passed in, just return 0.0 as default
return numberFormatter.stringFromNumber(NSDecimalNumber(string: "0.0"))!
}
else if (IS_NOT_A_DECIMAL_OR_ALREADY_A_CURRENCY_STRING)
{
//So obviously this would go here to see if it's not a decimal (or already contains a current placeholder etc)
}
else
{
return numberFormatter.stringFromNumber(NSDecimalNumber(string: self))!
}
}
Thank you for your help!
Sounds like you need to use NSScanner.
According to the docs, the scanDecimal function of NSScanner:
Skips past excess digits in the case of overflow, so the receiver’s
position is past the entire integer representation.
Invoke this method with NULL as value to simply scan past a decimal integer representation.
I've been mostly programming in Obj-C so my Swift is rubbish, but here's my attempt at translating the appropriate code for detecting numeric strings (as also demonstrated in this answer):
let scanner: NSScanner = NSScanner(string:self)
let isNumeric = scanner.scanDecimal(nil) && scanner.atEnd
If the string is not a decimal representation, isNumeric should return false.

Resources