I’m building a SwiftUI form where I’m using TextField with optional bindings. Here’s a simplified version of what I have:
import SwiftUI
struct ContentView: View {
@State private var name: PersonNameComponents? = nil
@State private var age: Int? = nil
@State private var password: String? = nil
var body: some View {
NavigationStack {
List {
TextField("Enter your full name", value: $name, format: .name(style: .medium))
TextField("Enter your age", value: $age, format: .number)
// This line doesn't compile
TextField("Enter your password", value: $password, format: <#T##ParseableFormatStyle#>)
}
}
}
}
As you can see, I’m using optional @State
variables for name
, age
, and password
. The first two work great because .name(style:)
and .number
are valid ParseableFormatStyles
for their respective types. However, I’m hitting a wall when it comes to the third TextField
.
I want to keep password as an optional String
(String?
) and use the TextField(_:value:format:)
initializer, but there doesn’t seem to be a built-in ParseableFormatStyle
that works with String
.
I’ve seen solutions that use the TextField(_:text:)
initializer with a non-optional String (@State var password: String = ""
), or even workarounds using a custom Binding
with get:
and set:
closures. However, I’d really like to avoid those approaches for the sake of consistency and simplicity, as I want this to work the same way the other two fields do.
Is there a clean way to make this work with an optional String
using TextField(_:value:format:)
, or is this just not supported?
You can either create your own ParseableFormatStyle
that formats an optional string, or write a computed property on Optional<String>
and access it through the Binding
. The latter is basically writing a custom Binding
using init(get:set:)
, but more readable.
The ParseableFormatStyle
can be written like this:
extension ParseableFormatStyle where Self == NilAsEmptyStringFormat {
static var nilAsEmptyString: NilAsEmptyStringFormat { .init() }
}
struct NilAsEmptyStringFormat: ParseableFormatStyle {
struct ParseStrategy: Foundation.ParseStrategy {
func parse(_ value: String) -> String? {
value.isEmpty ? nil : value
}
}
func format(_ value: String?) -> String {
value ?? ""
}
let parseStrategy: ParseStrategy
init() {
parseStrategy = .init()
}
}
// Usage:
TextField("Enter your password", value: $password, format: .nilAsEmptyString)
The property extension is simpler:
extension String? {
var nilAsEmpty: String {
get { self ?? "" }
set { self = newValue.isEmpty ? nil : newValue }
}
}
// Usage:
TextField("Enter your password", text: $password.nilAsEmpty)