Need help with writing `extension SwiftUI.Binding`

I need to use TextField on:

@State var text: String?    // optional

So I made a Binding on String?, look at the comments and give me some help:

import SwiftUI

// 1) is this the correct way to write this?
// if `toOptional` is a func, I can move the generic constaint to the func definition
// instead of at the outter `extension Binding`
// but I want this to be a var, look better at the call site
extension Binding where Value == String? {

    // 2) `toOptional`: is there a better name for this?
    public var toOptional: Binding<String>  {
        .init(get: { wrappedValue ?? "" }, set: { wrappedValue = $0.isEmpty ? nil : $0 })
    }

}

struct OptionalStringTextfield: View {
    @State var text: String?

    var body: some View {
        VStack {
            Text("\(text == nil ? "nil" : "\"\(text!)\"")")  // want "" around value so can't use nicer looking `??`
            TextField("Enter", text: $text.toOptional)  // <<< !! so I can use it here like this
        }
    }
}

Is it possible to make toOptional more generic such thatValue is not String? but Optional<T> where T == ??? (CustomStringConvertible??)?

You're removing the Optional from the binding's type, so the name toOptional seems backwards. I'd call it unwrap.

You can make it generic over the wrapped type (String in your original) by putting the generic constraint on the function itself instead of on the extension. However, in your String-specific version, you use "" to construct the default value (to replace nil) and you use .isEmpty to insert nil. Those won't work for all types. Instead you can pass those requirements as arguments. For example:

extension Binding {
    public func unwrap<Wrapped>(
        default: Wrapped,
        shouldBeNil: @escaping (Wrapped) -> Bool = { _ in false}
    ) -> Binding<Wrapped> where Value == Wrapped? {
        return .init(
            get: { wrappedValue ?? `default` },
            set: { self.wrappedValue = shouldBeNil($0) ? nil : $0 }
        )
    }
}

If the Wrapped type is Equatable, you can use that conformance to eliminate the shouldBeNil argument:

extension Binding {
    public func unwrap<Wrapped: Equatable>(
        default: Wrapped
    ) -> Binding<Wrapped> where Value == Wrapped? {
        return .init(
            get: { wrappedValue ?? `default` },
            set: { self.wrappedValue = $0 == `default` ? nil : $0 }
        )
    }
}

You can then write a String-specific version that calls the generic version like this:

extension Binding where Value == String? {
    public func unwrap() -> Binding<String> {
        return unwrap(default: "")
    }
}

1 Like