2

我正在SwiftUI使用 MVVM 构建一个应用程序。

我需要一些额外的文本字段行为,所以我将 a 包装UITextField在一个UIViewRepresentable视图中。

@State如果我在包含我的文本字段的视图中使用简单的文本来绑定文本,则自定义文本字段的行为符合预期;但由于我想将我的文本字段的所有文本存储在视图模型中,我使用的是@ObservedObject; 使用它时,文本字段绑定不起作用:它看起来总是重置为初始状态(空文本)并且它不发布任何值(并且视图不刷新)。 这种奇怪的行为只发生在UIViewRepresentable视图中。

我的主视图包含一个表单,它看起来像这样:

struct LoginSceneView: View {

    @ObservedObject private var viewModel: LoginViewModel = LoginViewModel()

    var body: some View {
        ScrollView(showsIndicators: false) {
            VStack(spacing: 22) {
                UIKitTextField(text: $viewModel.email, isFirstResponder: $viewModel.isFirstResponder)
                SecureField("Password", text: $viewModel.password)
                Button(action: {}) {
                    Text("LOGIN")
                }
                .disabled(!viewModel.isButtonEnabled)       
            }
            .padding(.vertical, 40)
        }
    }

}

视图模型是这样的:

class LoginViewModel: ObservableObject {

    @Published var email = ""
    @Published var password = ""
    @Published var isFirstResponder = false

    var isButtonEnabled: Bool { !email.isEmpty && !password.isEmpty }

}

最后,这是我的自定义文本字段:

struct UIKitTextField: UIViewRepresentable {

    // MARK: - Coordinator

    class Coordinator: NSObject, UITextFieldDelegate {

        private let textField: UIKitTextField

        fileprivate init(_ textField: UIKitTextField) {
            self.textField = textField

            super.init()
        }

        @objc fileprivate func editingChanged(_ sender: UITextField) {
            let text = sender.text ?? ""
            textField.text = text
            textField.onEditingChanged(text)
        }

        func textFieldDidBeginEditing(_ textField: UITextField) {
            self.textField.onEditingBegin()
        }

        func textFieldDidEndEditing(_ textField: UITextField) {
            self.textField.onEditingEnd()
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            self.textField.onReturnKeyPressed()
        }

    }

    // MARK: - Properties

    @Binding private var text: String
    private let onEditingChanged: (String) -> Void
    private let onEditingBegin: () -> Void
    private let onEditingEnd: () -> Void
    private let onReturnKeyPressed: () -> Bool

    // MARK: - Initializers

    init(text: Binding<String>,
         onEditingChanged: @escaping (String) -> Void = { _ in },
         onEditingBegin: @escaping  () -> Void = {},
         onEditingEnd: @escaping  () -> Void = {},
         onReturnKeyPressed: @escaping  () -> Bool = { true }) {
        _text = text
        self.onEditingChanged = onEditingChanged
        self.onEditingBegin = onEditingBegin
        self.onEditingEnd = onEditingEnd
        self.onReturnKeyPressed = onReturnKeyPressed
    }

    // MARK: - UIViewRepresentable methods

    func makeCoordinator() -> Coordinator { Coordinator(self) }

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
        textField.addTarget(context.coordinator, action: #selector(Coordinator.editingChanged(_:)), for: .editingChanged)
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
    }

}
4

2 回答 2

0

我已经编辑了你的代码,看看这是否是你要找的。

class LoginViewModel: ObservableObject {

    @Published var email = ""
    @Published var password = ""

    var isButtonEnabled: Bool { !email.isEmpty && !password.isEmpty }

}

struct LoginSceneView: View {

    @ObservedObject private var viewModel: LoginViewModel = LoginViewModel()

    var body: some View {


        ScrollView(showsIndicators: false) {
            VStack(spacing: 22) {
                UIKitTextField(text: $viewModel.email)
                SecureField("Password", text: $viewModel.password)
                Button(action: {
                    print("email is \(self.viewModel.email)")
                    print("password is \(self.viewModel.password)")
                    UIApplication.shared.endEditing()
                }) {
                    Text("LOGIN")
                }
                .disabled(!viewModel.isButtonEnabled)
            }
            .padding(.vertical, 40)
        }
    }

}

struct UIKitTextField: UIViewRepresentable {

    // MARK: - Coordinator

    class Coordinator: NSObject, UITextFieldDelegate {

        let textField: UIKitTextField

        fileprivate init(_ textField: UIKitTextField) {
            self.textField = textField

            super.init()
        }

        @objc fileprivate func editingChanged(_ sender: UITextField) {
            let text = sender.text ?? ""
            textField.text = text
            textField.onEditingChanged(text)
        }

        func textFieldDidBeginEditing(_ textField: UITextField) {
            self.textField.onEditingBegin()
        }

        func textFieldDidEndEditing(_ textField: UITextField) {
            self.textField.onEditingEnd()
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            self.textField.onReturnKeyPressed()
        }

    }

    // MARK: - Properties

    @Binding private var text: String
    private let onEditingChanged: (String) -> Void
    private let onEditingBegin: () -> Void
    private let onEditingEnd: () -> Void
    private let onReturnKeyPressed: () -> Bool


    // MARK: - Initializers

    init(text: Binding<String>,
         onEditingChanged: @escaping (String) -> Void = { _ in },
         onEditingBegin: @escaping  () -> Void = {},
         onEditingEnd: @escaping  () -> Void = {},
         onReturnKeyPressed: @escaping  () -> Bool = { true }) {
        _text = text
        self.onEditingChanged = onEditingChanged
        self.onEditingBegin = onEditingBegin
        self.onEditingEnd = onEditingEnd
        self.onReturnKeyPressed = onReturnKeyPressed

    }

    // MARK: - UIViewRepresentable methods

    func makeCoordinator() -> Coordinator { Coordinator(self) }

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        textField.setContentHuggingPriority(.defaultHigh, for: .vertical)
        textField.addTarget(context.coordinator, action: #selector(Coordinator.editingChanged(_:)), for: .editingChanged)
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
    }

}

我强烈建议不要让你的 UIViewRepresentable 变得复杂,因为isFirstResponder除非必要,否则使用 of 来做可能的替代方法。我假设您想将其用作关闭键盘的参数。有一些替代方案,例如:

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
于 2019-09-27T04:57:04.163 回答
0

您的问题可能UIViewRepresentable是每次更改某些绑定变量时都会创建。放置调试代码进行检查。

struct UIKitTextField: UIViewRepresentable {
    init() {
         print("UIViewRepresentable init()")
    }
    ...
}
于 2019-09-26T08:09:25.287 回答