9

Lets say i have something like this:

extension NSNumber{
    func toLocalCurrency(fractDigits:Int = 2)->String{
        let formatter = NSNumberFormatter()
        formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
        let userSettings:UserInfo? = UserInfo.first(sortDescriptors: nil, context: AERecord.defaultContext) as? UserInfo
        if let code = userSettings?.currency.name_short {
            formatter.currencyCode = code
        }
        formatter.maximumFractionDigits = fractDigits
        return formatter.stringFromNumber(self)!
    }
    func toLocalCurrencyWithoutFractionDigits()->String{
        return self.toLocalCurrency(fractDigits: 0)
    }
}

I want that to support as most of swift/mac number types as possible eg. CGFLoat NSNumber Int Float etc. But i dont want to repeat myself (copy paste and extend everything) or cast everywhere i want to use that function.

I tried to extend protocols like FloatLiteralType/Convertible but needs also casting. It "should" be possible to extend basic types in a more convenient way..

I also thought of global functions but they are less discoverable and feel more hacky.

Is there a nice way to achieve this in swift?

Harper04
  • 355
  • 2
  • 10
  • 1
    You could do it as a protocol and create a protocol extension. There is a wwdc video that will show this. Called Protocol Oriented Programming. – Fogmeister Jul 07 '15 at 09:50
  • Than i would have to use Swift 2 for default protocol implementations and extend every type manually. I am using Swift 1 for now.. – Harper04 Jul 07 '15 at 09:56
  • maybe something with a function with anyobject parameter http://stackoverflow.com/questions/29963848/swift-ios-how-to-use-inout-parameters-in-functions-with-anyobject-any-or-pointe – Michael Jul 07 '15 at 10:02
  • 2
    @mika, I disagree, there are normally always better alternatives to using `AnyObject`. – ABakerSmith Jul 07 '15 at 10:33

3 Answers3

11

As Sogmeister already said you will have to use Swift 2.0 to solve your problem.

Then you can do it like this:

// the solution right now is to implement it twice, I'll explain why
extension IntegerType {

    func toLocalCurrency(fractDigits:Int = 2) -> String {

        let formatter = NSNumberFormatter()
        formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle

        /* ... */

        formatter.maximumFractionDigits = fractDigits
        return formatter.stringFromNumber(self as! NSNumber)! // probably like this
    }

    func toLocalCurrencyWithoutFractionDigits() -> String {

        return self.toLocalCurrency(0)
    }
}

extension FloatingPointType {
    // second implementation goes here
}

// some example
let someUInt = UInt(12340)

someUInt.toLocalCurrency() // returns "12.340,00 €" for me

Updated answer:

The basic idea is to extend MyProtocol with default implementation of your functions and then extend IntegerType and FloatingPointType. But this won't happen in Swift 2.0 (see here). The reason why it's not working yet is here. Here is another solution, which is better then my first one.

protocol MyProtocol {}

extension MyProtocol {

    func toLocalCurrency(fractDigits:Int = 2) -> String {

        let formatter = NSNumberFormatter()
        formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle

        /* ... */

        formatter.maximumFractionDigits = fractDigits
        guard let newNumber = self as? NSNumber else { fatalError("this type is not convertable to NSNumber") }
        return formatter.stringFromNumber(newNumber)!
    }

    func toLocalCurrencyWithoutFractionDigits() -> String {

        return self.toLocalCurrency(0)
    }
}

/* extend your number types you need */
extension Int : MyProtocol {} 
extension Double : MyProtocol {} // done
DevAndArtist
  • 4,971
  • 1
  • 23
  • 48
  • 1
    Thanks, seems like this as close as we can get (i dont like that there is no root number type in swift) But i'll have to wait until all libraries are updated to swift 2.0 – Harper04 Jul 12 '15 at 11:21
9

We could create a protocol, extend it with a default implementation and make all our numeric types conform to it:

protocol FormattableNumeric {}
extension FormattableNumeric {
    var localized: String {
        guard let number = self as? NSNumber else { return "NaN" }
        return number.description(withLocale: Locale.current)
    }
}
extension Int: FormattableNumeric {}
extension UInt: FormattableNumeric {}
extension Float: FormattableNumeric {}
extension Double: FormattableNumeric {}
// etc.

Depending on the current locale, you can now get formatted numbers just so:

1000.localized // "1,000"
12_345_678.localized // "12,345,678"
(1_000_000 * Double.pi).localized // "3,141,592.65358979"

Of course, for more control over the formatting, we could also use NumberFormatter in our implementation:

return NumberFormatter.localizedString(from: number, number: .decimal)
markiv
  • 1,578
  • 16
  • 12
1

Details

  • Xcode 10.1 (10B61)
  • Swift 4.2

Solution

extension NSNumber {
    func toLocalCurrency(fractDigits: Int = 2) -> String? {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.maximumFractionDigits = fractDigits
        return formatter.string(from: self)
    }
    func toLocalCurrencyWithoutFractionDigits() -> String? {
        return toLocalCurrency(fractDigits: 0)
    }
}

extension Numeric {
    func toLocalCurrency(fractDigits: Int = 2) -> String? {
        return (self as? NSNumber)?.toLocalCurrency(fractDigits: fractDigits)
    }
    func toLocalCurrencyWithoutFractionDigits() -> String? {
        return toLocalCurrency(fractDigits: 0)
    }
}

Sample

let value = 34.234

func test<T: Numeric>(value: T) {
    let toLocalCurrency = value.toLocalCurrency()
    let result = toLocalCurrency != nil ? "\(toLocalCurrency!)" : "nil"
    print(" type: \(type(of: value)), toLocalCurrency: \(result)")
}

func test<T: NSNumber>(value: T) {
    let toLocalCurrency = value.toLocalCurrency()
    let result = toLocalCurrency != nil ? "\(toLocalCurrency!)" : "nil"
    print(" type: \(type(of: value)), toLocalCurrency: \(result)")
}

test(value: Int8(value))
test(value: Int16(value))
test(value: Int32(value))
test(value: Int(value))
test(value: Float(value))
test(value: Int16(value))
test(value: Int32(value))
test(value: Int8(value))
test(value: Double(value))
test(value: CGFloat(value))
test(value: NSNumber(value: value))

Result

enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127