无法在 Swift UI 中显示带有视图层和 onAppear 的视图
Can't show view in Swift UI with layers of views and onAppear
有一种奇怪的情况,如果您通过另一个视图显示一个视图,则当使用 onAppear 设置值时,第二个视图的内容(3 个项目的列表)将不会显示。我猜 SwiftUI 会感到困惑,因为第二个视图 onAppear 在第一个视图 onAppear 之前被调用,但我仍然认为这很奇怪,因为两个视图数据仅在它们自己的视图中使用。此外,如果我不使用视图模型而是直接在视图中使用状态设置数据也没有问题,但是还有另一个问题,即视图模型声明必须被注释掉,否则我会得到“Thread 1 : EXC_BAD_ACCESS(代码=1,地址=0x400000008)”。此外,如果我在显示第二个视图之前在第一个视图中检查在那里设置的数据的 nil ,那么第二个视图将在您第一次导航时显示(到包含第二个的第一个视图),但不会显示其他时间。我还尝试删除内容视图并直接从 FirstView 开始,然后屏幕就变成黑色了。我想了解为什么会发生这些问题,通过 init 设置数据是有效的,但是在导航到它之前将调用 init,因为这就是 NavigationView 的工作方式,反过来我想我可以通过使用延迟视图来解决,但有一些情况我也想在后台使用 .task 做一些事情,它与 .onAppear 有同样的问题。无论如何,我想避免变通并理解问题。请参阅评论以获得更好的解释:
struct ContentView: View {
var body: some View {
NavigationView {
// If I directly go to SecondView instead the list shows
NavigationLink(destination: FirstView()) {
Text("Go to first view")
}
}
}
}
class FirstViewViewModel: ObservableObject {
@Published var listOfItems: [Int]?
func updateList() {
listOfItems = []
}
}
struct FirstView: View {
@ObservedObject var viewModel = FirstViewViewModel()
// If I have the state in the view instead of the view model there is no problem.
// Also need to comment out the view model when using the state otherwise I get Thread 1: EXC_BAD_ACCESS runtime exception
//@State private var listOfItems: [Int]?
var body: some View {
// Showing SecondView without check for nil and it will never show
SecondView()
// If I check for nil then the second view will show the first time its navigated to, but no other times.
/*Group {
if viewModel.listOfItems != nil {
SecondView()
} else {
Text("Loading").hidden() // Needed in order for onAppear to trigger, EmptyView don't work
}
}*/
// If I comment out onAppear there is no problem
.onAppear {
print("onAppear called for first view after onAppear in second view")
viewModel.updateList()
// listOfItems = []
}
}
}
class SecondViewViewModel: ObservableObject {
@Published var listOfItems = [String]()
func updateList() {
listOfItems = ["first", "second", "third"]
}
}
struct SecondView: View {
@ObservedObject var viewModel = SecondViewViewModel()
// If I set the items through init instead of onAppear the list shows every time
init() {
// viewModel.updateList()
}
var body: some View {
Group {
List {
ForEach(viewModel.listOfItems, id: \.self) { itemValue in
VStack(alignment: .leading, spacing: 8) {
Text(itemValue)
}
}
}
}
.navigationTitle("Second View")
.onAppear {
viewModel.updateList()
// The items are printed even though the view don't show
print("items: \(viewModel.listOfItems)")
}
}
}
我们不在 SwiftUI 中使用视图模型对象。对于视图的瞬态数据,我们使用 @State
和 @Binding
使视图数据结构表现得像一个对象。
仅供参考,使用 @ObservedObject
初始化对象是导致内存泄漏的错误,每次 View
结构初始化时都会被丢弃。当我们创建一个 Combine loader/fetcher 对象时,我们希望将生命周期绑定到视图,我们使用 @StateObject
.
初始化对象
此外,您不能对值类型数组使用 id: \.self
和 ForEach
,因为它会在数据更改时崩溃。您必须为您的数据创建一个符合 Identifiable
的结构,才能与 ForEach
一起使用。或者如果你真的想要静态 ForEach
你可以做 ForEach(0..<5) {
有一种奇怪的情况,如果您通过另一个视图显示一个视图,则当使用 onAppear 设置值时,第二个视图的内容(3 个项目的列表)将不会显示。我猜 SwiftUI 会感到困惑,因为第二个视图 onAppear 在第一个视图 onAppear 之前被调用,但我仍然认为这很奇怪,因为两个视图数据仅在它们自己的视图中使用。此外,如果我不使用视图模型而是直接在视图中使用状态设置数据也没有问题,但是还有另一个问题,即视图模型声明必须被注释掉,否则我会得到“Thread 1 : EXC_BAD_ACCESS(代码=1,地址=0x400000008)”。此外,如果我在显示第二个视图之前在第一个视图中检查在那里设置的数据的 nil ,那么第二个视图将在您第一次导航时显示(到包含第二个的第一个视图),但不会显示其他时间。我还尝试删除内容视图并直接从 FirstView 开始,然后屏幕就变成黑色了。我想了解为什么会发生这些问题,通过 init 设置数据是有效的,但是在导航到它之前将调用 init,因为这就是 NavigationView 的工作方式,反过来我想我可以通过使用延迟视图来解决,但有一些情况我也想在后台使用 .task 做一些事情,它与 .onAppear 有同样的问题。无论如何,我想避免变通并理解问题。请参阅评论以获得更好的解释:
struct ContentView: View {
var body: some View {
NavigationView {
// If I directly go to SecondView instead the list shows
NavigationLink(destination: FirstView()) {
Text("Go to first view")
}
}
}
}
class FirstViewViewModel: ObservableObject {
@Published var listOfItems: [Int]?
func updateList() {
listOfItems = []
}
}
struct FirstView: View {
@ObservedObject var viewModel = FirstViewViewModel()
// If I have the state in the view instead of the view model there is no problem.
// Also need to comment out the view model when using the state otherwise I get Thread 1: EXC_BAD_ACCESS runtime exception
//@State private var listOfItems: [Int]?
var body: some View {
// Showing SecondView without check for nil and it will never show
SecondView()
// If I check for nil then the second view will show the first time its navigated to, but no other times.
/*Group {
if viewModel.listOfItems != nil {
SecondView()
} else {
Text("Loading").hidden() // Needed in order for onAppear to trigger, EmptyView don't work
}
}*/
// If I comment out onAppear there is no problem
.onAppear {
print("onAppear called for first view after onAppear in second view")
viewModel.updateList()
// listOfItems = []
}
}
}
class SecondViewViewModel: ObservableObject {
@Published var listOfItems = [String]()
func updateList() {
listOfItems = ["first", "second", "third"]
}
}
struct SecondView: View {
@ObservedObject var viewModel = SecondViewViewModel()
// If I set the items through init instead of onAppear the list shows every time
init() {
// viewModel.updateList()
}
var body: some View {
Group {
List {
ForEach(viewModel.listOfItems, id: \.self) { itemValue in
VStack(alignment: .leading, spacing: 8) {
Text(itemValue)
}
}
}
}
.navigationTitle("Second View")
.onAppear {
viewModel.updateList()
// The items are printed even though the view don't show
print("items: \(viewModel.listOfItems)")
}
}
}
我们不在 SwiftUI 中使用视图模型对象。对于视图的瞬态数据,我们使用 @State
和 @Binding
使视图数据结构表现得像一个对象。
仅供参考,使用 @ObservedObject
初始化对象是导致内存泄漏的错误,每次 View
结构初始化时都会被丢弃。当我们创建一个 Combine loader/fetcher 对象时,我们希望将生命周期绑定到视图,我们使用 @StateObject
.
此外,您不能对值类型数组使用 id: \.self
和 ForEach
,因为它会在数据更改时崩溃。您必须为您的数据创建一个符合 Identifiable
的结构,才能与 ForEach
一起使用。或者如果你真的想要静态 ForEach
你可以做 ForEach(0..<5) {