![](/img/trans.png)
[英]How to use UIApplicationDelegateAdaptor as ObservableObject?
[英]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.