SwiftUI Custom TextField with UIViewRepresentable Issue with ObservableObject and pushed View

混江龙づ霸主 提交于 2021-01-05 08:42:52

问题


I created a UIViewRepresentable to wrap UITextField for SwiftUI, so I can e.g. change the first responder when the enter key was tapped by the user.

This is my UIViewRepresentable (I removed the first responder code to keep it simple)

struct CustomUIKitTextField: UIViewRepresentable {

    @Binding var text: String
    var placeholder: String

    func makeUIView(context: UIViewRepresentableContext<CustomUIKitTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        textField.placeholder = placeholder
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomUIKitTextField>) {
        uiView.text = text
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiView.setContentCompressionResistancePriority(.required, for: .vertical)
    }

    func makeCoordinator() -> CustomUIKitTextField.Coordinator {
        Coordinator(parent: self)
    }

    class Coordinator: NSObject, UITextFieldDelegate {
        var parent: CustomUIKitTextField

        init(parent: CustomUIKitTextField) {
            self.parent = parent
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            parent.text = textField.text ?? ""
        }        

    }
}

The first screen of the app has a "Sign in with email" button which pushes MailView that displays a CustomUIKitTextField and uses a @Published property of an ObservableObject view model as the TextField's text.

struct MailView: View {

    @ObservedObject var viewModel: MailSignUpViewModel

    var body: some View {
        VStack {
            CustomUIKitTextField(placeholder: self.viewModel.placeholder,
                             text: self.$viewModel.mailAddress)
                .padding(.top, 30)
                .padding(.bottom, 10)

            NavigationLink(destination: UsernameView(viewModel: UsernameSignUpViewModel())) {
                Text("Next")
            }

            Spacer()            
        }
    }
}

Everything works fine until I push another view like MailView, say e.g. UsernameView. It is implemented exactly in the same way, but somehow the CustomUIKitTextField gets an updateUIView call with an empty string once I finish typing.

There is additional weird behavior like when I wrap MailView and UsernameView in another NavigationView, everything works fine. But that is obviously not the way to fix it, since I would have multiple NavigationViews then.

It also works when using an @State property instead of a @Published property inside a view model. But I do not want to use @State since I really want to keep the model code outside the view.

Is there anybody who faced the same issue or a similar one?


回答1:


It looks like you’re using the wrong delegate method. textFieldDidChangeSelection will produce some inconsistent results (which is what it sounds like you’re dealing with). Instead, I recommend using textFieldDidEndEditing which will also give you access to the passed in control, but it guarantees that you’re getting the object as it is resigning the first responder. This is important because it means you’re getting the object after the properties have been changed and it’s releasing the responder object.

So, I would change your delegate method as follows:


func textFieldDidEndEditing(_ textField: UITextField) {
    parent.text = textField.text ?? ""
}

For more info, see this link for the textFieldDidEndEditing method: https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619591-textfielddidendediting

And this link for info on the UITextFieldDelegate object: https://developer.apple.com/documentation/uikit/uitextfielddelegate

EDIT

Based on the comment, if you're looking to examine the text everytime it changes by one character, you should implement this delegate function:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

     // User pressed the delete key
     if string.isEmpty {
        // If you want to update your query string here after the delete key is pressed, you can rebuild it like we are below
        return true
     }

     //this will build the full string the user intends so we can use it to build our search
     let currentText = textField.text ?? ""
     let replacementText = (currentText as NSString).replacingCharacters(in: range, with: string)
     // You can set your parent.text here if you like, or you can fire the search function as well

     // When you're done though, return true to indicate that the textField should display the changes from the user

     return true
}


来源:https://stackoverflow.com/questions/59990634/swiftui-custom-textfield-with-uiviewrepresentable-issue-with-observableobject-an

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!