'SwiftUI Optional TextField
Can SwiftUI Text Fields work with optional Bindings? Currently this code:
struct SOTestView : View {
@State var test: String? = "Test"
var body: some View {
TextField($test)
}
}
produces the following error:
Cannot convert value of type 'Binding< String?>' to expected argument type 'Binding< String>'
Is there any way around this? Using Optionals in data models is a very common pattern - in fact it's the default in Core Data so it seems strange that SwiftUI wouldn't support them
Solution 1:[1]
You can add this operator overload, then it works as naturally as if it wasn't a Binding.
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}
This creates a Binding that returns the left side of the operator's value if it's not nil, otherwise it returns the default value from the right side.
When setting it only sets lhs value, and ignores anything to do with the right hand side.
It can be used like this:
TextField("", text: $test ?? "default value")
Solution 2:[2]
True, at the moment TextField in SwiftUI can only be bound to String variables, not String?.
But you can always define your own Binding like so:
import SwiftUI
struct SOTest: View {
@State var text: String?
var textBinding: Binding<String> {
Binding<String>(
get: {
return self.text ?? ""
},
set: { newString in
self.text = newString
})
}
var body: some View {
TextField("Enter a string", text: textBinding)
}
}
Basically, you bind the TextField text value to this new Binding<String> binding, and the binding redirects it to your String? @State variable.
Solution 3:[3]
Try this works for me with reusable function
@State private var name: String? = nil
private func optionalBinding<T>(val: Binding<T?>, defaultVal: T)-> Binding<T>{
Binding<T>(
get: {
return val.wrappedValue ?? defaultVal
},
set: { newVal in
val.wrappedValue = newVal
}
)
}
// Usage
TextField("", text: optionalBinding(val: $name, defaultVal: ""))
Solution 4:[4]
I prefer the answer provided by @Jonathon. as it is simple and elegant and provides the coder with an insitu base case when the Optional is .none (= nil) and not .some.
However I feel it is worth adding in my two cents here. I learned this technique from reading Jim Dovey's blog on SwiftUI Bindings with Core Data. Its essentially the same answer provided by @Jonathon. but does include a nice pattern that can be replicated for a number of different data types.
First create an extension on Binding
public extension Binding where Value: Equatable {
init(_ source: Binding<Value?>, replacingNilWith nilProxy: Value) {
self.init(
get: { source.wrappedValue ?? nilProxy },
set: { newValue in
if newValue == nilProxy { source.wrappedValue = nil }
else { source.wrappedValue = newValue }
}
)
}
}
Then use in your code like this...
TextField("", text: Binding($test, replacingNilWith: String()))
or
TextField("", text: Binding($test, replacingNilWith: ""))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jonathan. |
| Solution 2 | Frederic Adda |
| Solution 3 | Tun Cham Roeun |
| Solution 4 |
