Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend all number types in Swift

Tags:

swift

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?

like image 298
Harper04 Avatar asked Jul 07 '15 09:07

Harper04


3 Answers

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

like image 188
Vasily Bodnarchuk Avatar answered Oct 16 '22 13:10

Vasily Bodnarchuk


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
like image 11
DevAndArtist Avatar answered Oct 17 '22 05:10

DevAndArtist


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)
like image 9
markiv Avatar answered Oct 17 '22 04:10

markiv