我一直在寻找非常奇怪的SwiftUI错误,这些错误仅在非常特殊的情况下才会出现?。例如,我有一个显示为模型表的表格。此表单具有ViewModel,并显示UITextView
(通过UIViewRepresentable
和@Binding
-都在下面的代码中)。
一切正常,您可以运行下面的代码,您将看到所有双向绑定都按预期工作:在一个字段中键入,在另一个字段中更改,反之亦然。但是,一旦取消注释行@Environment(\.presentationMode) private var presentationMode
,TextView中的双向绑定就会中断。您还将注意到ViewModel两次打印“ HERE”。
到底是怎么回事?我的猜测是,ContentView
一旦显示出模态,presentationMode
的值就会改变,然后重新呈现工作表(因此FormView
)。那将解释重复的“ HERE”被记录。但是,为什么这会破坏双向文本绑定?
一种解决方法是不使用ViewModel,而直接在@State
中拥有一个FormView
属性。但这不是一个很好的解决方案,因为我在现实世界的表单中有很多逻辑,而我不想转移到表单视图。那么,有人有更好的解决方案吗?
import SwiftUI
import UIKit
struct TextView: UIViewRepresentable {
@Binding var text: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextView {
let uiTextView = UITextView()
uiTextView.delegate = context.coordinator
return uiTextView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = self.text
}
class Coordinator : NSObject, UITextViewDelegate {
var parent: TextView
init(_ view: TextView) {
self.parent = view
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
func textViewDidEndEditing(_ textView: UITextView) {
self.parent.text = textView.text
}
}
}
struct ContentView: View {
@State private var showForm = false
//@Environment(\.presentationMode) private var presentationMode
var body: some View {
NavigationView {
Text("Hello")
.navigationBarItems(trailing: trailingNavigationBarItem)
}
.sheet(isPresented: $showForm) {
FormView()
}
}
private var trailingNavigationBarItem: some View {
Button("Form") {
self.showForm = true
}
}
}
struct FormView: View {
@ObservedObject private var viewModel = ViewModel()
var body: some View {
NavigationView {
Form {
Section(header: Text(viewModel.text)) {
TextView(text: $viewModel.text)
.frame(height: 200)
}
Section(header: Text(viewModel.text)) {
TextField("Text", text: $viewModel.text)
}
}
}
}
}
class ViewModel: ObservableObject {
@Published var text = ""
init() {
print("HERE")
}
}
答案 0 :(得分:0)
我终于找到了一种解决方法:将ViewModel存储在ContentView上而不是FormView上,然后将其传递给FormView。
struct ContentView: View {
@State private var showForm = false
@Environment(\.presentationMode) private var presentationMode
private let viewModel = ViewModel()
var body: some View {
NavigationView {
Text("Hello")
.navigationBarItems(trailing: trailingNavigationBarItem)
}
.sheet(isPresented: $showForm) {
FormView(viewModel: self.viewModel)
}
}
private var trailingNavigationBarItem: some View {
Button("Form") {
self.showForm = true
}
}
}
struct FormView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
NavigationView {
Form {
Section(header: Text(viewModel.text)) {
TextView(text: $viewModel.text)
.frame(height: 200)
}
Section(header: Text(viewModel.text)) {
TextField("Text", text: $viewModel.text)
}
}
}
}
}
class ViewModel: ObservableObject {
@Published var text = ""
init() {
print("HERE")
}
}
唯一的问题是,即使从未打开FormView,打开ContentView时也立即实例化了ViewModel。感觉有点浪费。尤其是当您的列表很大时,通过NavigationLink链接到一堆详细信息页面,即使您从未离开过List页面,现在这些页面都可以预先创建其Form-as-a-Form FormView的ViewModel。
可悲的是,我无法将ViewModel转换为结构,因为我实际上需要(异步)更改状态,然后最终遇到Escaping closure captures mutating 'self' parameter
编译器错误。叹。是的,我坚持使用类。
问题的根源仍然是两次对FormView进行实例化(由于@Environment(\.presentationMode)
),这也导致创建了两个ViewModel(我的解决方法是将一个副本基本传递给两个FormViews来解决)。但是,由于标准TextField确实按预期工作,因此中断@Binding
仍然很奇怪。
SwiftUI仍然有很多奇怪的陷阱,我真的希望这很快变得更容易管理。如果有人能解释工作表,ObservableObject
类(视图模型),@Environment(\.presentationMode)
和@Binding
放在一起的行为,我真是无所适从。