我正在使用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
}
}
答案 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)
}
}