Search code examples
swiftswiftuicombine

How to remove special characters from the `TextField` in SwiftUI


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.


Solution

  • 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))
                }
        }
    }