Home > Net >  Creating an Email Input Field in SwiftUI
Creating an Email Input Field in SwiftUI

Time:03-18

I'm trying to create an email input field in SwiftUI 2 that allows only certain characters to be entered. The code here is based on a piece of code from https://stackoverflow.com/a/57829567/356105.

The code itself works but since this is a reusable view component I want to provide a text property from a parent view's view model to the EmailTextField which is updated when the text input changes ...

EmailTextField view:

import SwiftUI
import Combine

struct EmailTextField: View {
    private class EmailTextFieldViewModel: ObservableObject {
        @Published var text = String.Empty
        private var subCancellable: AnyCancellable!
        private var validCharSet = CharacterSet(charactersIn: "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._- $!~&=#[]@")

        init() {
            subCancellable = $text.sink {
                value in

                /* Check if the new string contains any invalid characters. */
                if value.rangeOfCharacter(from: self.validCharSet.inverted) != nil {
                    /* Clean the string (do this on the main thread to avoid overlapping with the current ContentView update cycle). */
                    DispatchQueue.main.async {
                        self.text = String(self.text.unicodeScalars.filter {
                            self.validCharSet.contains($0)
                        })
                    }
                }
            }
        }

        deinit {
            subCancellable.cancel()
        }
    }

    @ObservedObject private var viewModel = EmailTextFieldViewModel()
    private let placeHolder: String

    var body: some View {
        TextField(placeHolder, text: $viewModel.text)
            .keyboardType(.emailAddress)
            .autocapitalization(.none)
            .disableAutocorrection(true)
    }

    init(_ placeHolder: String = .Empty, text: Binding<String>) {
        self.placeHolder = placeHolder
    }
}

In the parent view I'm trying this:

var body: some View {
    VStack {
        EmailTextField("Email", text: $viewModel.email)
            .onChange(of: viewModel.email, perform: onEmailInputChanged)
    }
}

private func onEmailInputChanged(changedEmail: String) {
    // Nothing happens here!
    print("\(changedEmail)")
}

How do I need to change the EmailTextField code to be able to bind the text variable in its view model to the text: Binding<String> argument in its constructor?

CodePudding user response:

Here is an alternative to your (somewhat complicated) code. I think it is simpler, provides a reusable EmailInputView and calls to onEmailInputChanged, and works well for me:

import SwiftUI
import Combine

class ViewModel: ObservableObject {
    @Published var email = ""
}

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        VStack {
            EmailInputView(placeHolder: "Email", txt: $viewModel.email)
                .onChange(of: viewModel.email, perform: onEmailInputChanged)
        }
    }
    
    private func onEmailInputChanged(changedEmail: String) {
        print("-----> in onEmailInputChanged: \(changedEmail) ")
    }
}

struct EmailInputView: View {
    var placeHolder: String = ""
    @Binding var txt: String
    
    var body: some View {
        TextField(placeHolder, text: $txt)
            .keyboardType(.emailAddress)
            .onReceive(Just(txt)) { newValue in
                let validString = newValue.filter { "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._- $!~&=#[]@".contains($0) }
                if validString != newValue {
                    self.txt = validString
                }
        }
    }
}
  • Related