繁体   English   中英

如何将 ObservableObject 与 UIViewRepresentable 一起使用

[英]How to use ObservableObject with UIViewRepresentable

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

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

如果我在包含我的文本字段的视图中使用简单的@State来绑定文本,则自定义文本字段的行为与预期一样; 但由于我想将我的文本字段的所有文本存储在视图 model 中,所以我使用的是@ObservedObject 使用它时,文本字段绑定不起作用:它看起来总是重置为初始 state(空文本)并且它不发布任何值(并且视图不刷新)。 这种奇怪的行为只发生在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)
        }
    }

}

视图 model 是这样的:

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
    }

}

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM