如何将ObservableObject与UIViewRepresentable一起使用

时间:2019-09-25 12:59:28

标签: ios swift swiftui ios13 swift5.1

我正在使用MVVM构建SwiftUI应用。

我需要一些其他用于文本字段的行为,因此我将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
    }

}

2 个答案:

答案 0 :(得分:0)

您的问题可能是,每次更改某些绑定var时都会创建UIViewRepresentable。检查调试代码。

struct UIKitTextField: UIViewRepresentable {
    init() {
         print("UIViewRepresentable init()")
    }
    ...
}

答案 1 :(得分: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等功能,除非有必要。我假设您想将其用作关闭键盘的参数。有一些替代方法,例如:

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}