I need to apply some validations in my form like remove special character, accept only number, alphabets, alphanumeric, and only specific length of a string.
I have many text fields, in many places in my app. So that I'm creating extensions to Binding, and trying to apply conditions when editing.
These filters/conditions using for @State
, @Published
, and @Binding
variables.
Here is my code:
struct ContentView: View {
@State var name = ""
var body: some View {
InputField(text: $name)
}
}
struct InputField: View {
@Binding var text: String
var body: some View {
VStack {
TextField("Name here...", text: $text.limit(6).alphaNumeric)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .leading)
}.padding()
}
}
extension Binding where Value == String {
var alphaNumeric: Binding<String> {
Binding<String>(get: { self.wrappedValue },
set: {
self.wrappedValue = $0.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)})
}
func limit(_ length: Int) -> Binding<String> {
Binding<String>(get: { self.wrappedValue },
set: {
print($0.prefix(length))
self.wrappedValue = String($0.prefix(length))
})
}
}
Here in the above code $text.limit(6).alphaNumeric
, I'm trying to limit the length to 6 characters and only allow the alphaNumeric string.
How about a custom TextField with checks, instead of messing with Binding Wrapper itself: (with OPs full code to proof that binding works)
struct ContentView: View {
@State var name = ""
@State var number = ""
var body: some View {
InputField(text: $name, number: $number)
}
}
struct InputField: View {
@Binding var text: String
@Binding var number: String
var body: some View {
VStack {
TextFieldWithCheck("Name here...", text: $text, limit: 15, allowed: .alphanumerics)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .leading)
TextFieldWithCheck("Phone no here...", text: $number, limit: 9, allowed: .decimalDigits)
.frame(maxWidth: .infinity, minHeight: 48, alignment: .leading)
}.padding()
}
}
// here is the custom TextField itself
struct TextFieldWithCheck: View {
let label: LocalizedStringKey
@Binding var text: String
let limit: Int
let allowed: CharacterSet
init(_ label: LocalizedStringKey, text: Binding<String>, limit: Int = Int.max, allowed: CharacterSet = .alphanumerics) {
self.label = label
self._text = Binding(projectedValue: text)
self.limit = limit
self.allowed = allowed
}
var body: some View {
TextField(label, text: $text)
.onChange(of: text) { _ in
// all credits to Leo Dabus:
text = String(text.prefix(limit).unicodeScalars.filter(allowed.contains))
}
}
}