Diesel
Diesel

Reputation: 5345

Accept Float, Double or Int in Swift `init` to convert to String

I'm attempting to convert a value to a string in an init. That value can be an Int, Double, or Float.

For example:

struct example {
    var string: String
    
    init(number: Int) {
        string = String(number)
    }
}

I would like to say something like below (I'm using Typescript as an example - this obviously doesn't work, but I want number to be any of those three types - all of which String can convert).

struct example {
    var string: String
    
    init(number: Int | Float | Double) {
        string = String(number)
    }
}

Edit: I realized I have another issue. I need to convert back from a string to the type of Int or Double or Float. Using one of the answers below, I'm trying to figure out how to implement getNumberWith5:

struct example2<N: Numeric & CustomStringConvertible>: CustomStringConvertible {
    @Binding var number: N
    
    init(number: Binding<N>) {
        self._number = number
    }
    var description: String {
        String(describing: number)
    }
    
    mutating func getNumberWith5() {
        // How do I update number to the type N?
        self.number = howDoIConvertToN(description + "5")
    }
}

Or from another answer:

struct example3<N: Numeric> {

    @Binding var number: N
    var string: String

    init(number: Binding<N>) {
        self._number = number
        self.string = "\(number)"
    }
    
    mutating func getNumberWith5() {
        // How do I update number to the type N?
        self.number = howDoIConvertToN(string + "5")
    }
}

Edit2 My Answer:

I attempted to create an equivalent of type unions (as Typescript has) using enums in Swift based on this article. But it was challenging to then assign back to that value. I've decided Swift just doesn't have first class support for type unions like Typescript has. So, I used the accepted answer below and this seems to work.

extension String {
    func numeric<N: Numeric & LosslessStringConvertible>() -> N? {
        N(self)
    }
}

struct example4<N: Numeric & LosslessStringConvertible> {
    @State var string: String
    @Binding var number: N

    init(number: Binding<N>) {
        self._number = number
        self.string = String(describing: number)
    }

    mutating func getNumberWith5() {
        let newString = string + "5"
        number = newString.numeric() ?? 0
    }
}

Upvotes: 2

Views: 1301

Answers (4)

AnderCover
AnderCover

Reputation: 2661

Actually if all you want is a string representation of Int Float Double or any other standard numeric type you only need to know that they conform to CustomStringConvertible and use String(describing:).

Or you can use conformance to Numeric and CustomStringConvertible:

struct example {
    var string: String
    
    init<C: CustomStringConvertible & Numeric>(number: C) {
        string = String(describing: number)
    }
}

and maybe even better example itself could conform to CustomStringConvertible

struct example: CustomStringConvertible {
    var description: String
    
    init<C: CustomStringConvertible & Numeric>(number: C) {
        description = String(describing: number)
    }
}

yet another way :

struct example<N: Numeric & CustomStringConvertible>: CustomStringConvertible {
    let number: N
    init(number: N) {
        self.number = number
    }
    var description: String {
        String(describing: number)
    }
}

EDIT

I think what you want is a custom Property Wrapper not @Binding:

@propertyWrapper struct CustomStringConversion<Wrapped: CustomStringConvertible> {
    var wrappedValue: Wrapped

    init(wrappedValue: Wrapped) {
        self.wrappedValue = wrappedValue
    }
    
    var projectedValue: String { .init(describing: wrappedValue) }
}

struct Foo {
    @CustomStringConversion var number = 5
}

let foo = Foo()
let number: Int = foo.number // 5
let stringRepresentation: String = foo.$number // "5"

But as @LeoDabus pointed out using LosslessStringConvertible may be better :

struct example<N: Numeric & LosslessStringConvertible>: LosslessStringConvertible {
    let number: N
    
    init(number: N) {
        self.number = number
    }
    
    init?(_ description: String) {
        guard let number = N(description) else { return nil }
        self.number = number
    }
    
    var description: String {
        .init(number)
    }
}

let bar = example(number: Double.greatestFiniteMagnitude) // 1.7976931348623157e+308
let baz: example<Double>? = example("1.7976931348623157e+308") // 1.7976931348623157e+308

Upvotes: 5

Raja Kishan
Raja Kishan

Reputation: 18944

Use generic structure with Numeric protocol.

struct Example<T:Numeric> {
    var string: String
    init(number: T) {
        self.string = "\(number)"
    }
}

Upvotes: 2

Schottky
Schottky

Reputation: 2024

You can have a look at how swift does this with its String-initializer:

struct Example {
    init<Number>(number: Number) where Number: BinaryInteger {
        string = String(number)
    }
}

Upvotes: -1

lorem ipsum
lorem ipsum

Reputation: 29329

struct Example {
    var string: String
    
    init(number: Int) {
        string = String(number)
    }
    init(number: Float) {
        string = String(number)
    }
    init(number: Double) {
        string = String(number)
    }
}

Upvotes: 1

Related Questions