I have a bug when using NavigationLinks and an ObservableObject. I don't quite understand why because I don't understand what is happening to the views and data as I am navigating. This is some pseudo-code to illustrate the problem:
class Settings: ObservableObject {
@Published var data: [Int] = [0, 1, 2, 3, 4, 5]
}
struct ContentView: View {
@State var new_view: Bool = false
@ObservedObject var content_view_settings = Settings()
var body: some View {
NavigationView {
VStack {
Button(action: {
DeleteLastItem()
}) {
Text("Delete last item")
}
Button(action: {
self.new_view = true
}) {
Text("New View")
}
NavigationLink(destination: NewView(new_view_settings: content_view_settings), isActive: $new_view) {
EmptyView()
}
}
}
}
}
struct NewView: View {
@ObservedObject var new_view_settings: Settings
@State var index = -1
var body: some View {
VStack {
Button(action: {
self.index = self.new_view_settings.count - 1
}) {
Text("change index")
}
if self.index > -1 {
Text("\(self.new_view_settings.data[index])")
}
}
}
}
The description of the problem is this:
I have a view with an ObservedObject that I pass to a subsequent view upon navigating. This sub-view accesses the last element of the array, but it only does that once the index variable is validated through a button click. The text is then rendered only after the index is validated.
Now, suppose I validate the index so it would equal 5 in this example. Then I navigate back to the original view. If I delete the last element, the index 5 is no longer valid. As soon as I delete that last element I get an invalid index error and the simulator crashes.
But let's say I navigate backward and do not delete the last element. Then when I navigate forward, the index variable is reset.
Since I get the crash, this means the view is still alive and being updated or something but when I navigate to it once again the view is reloaded. Does this mean the view is alive until it gets initialized again? This is contrived code but it is essentially the issue I am having. I thought the original code would be a bit harder to understand.
Does this mean the view is alive until it gets initialized again?
Yes, the view may be alive even after you navigate back to the parent view.
To better understand what's happening run the same code on the iPad simulator (preferably in the horizontal mode). You'll notice that the NavigationView
is split in two parts: master and detail - this way you can see both parent and child view at once.
Now, if you perform the same experiment from your question, you'll see the child view remains present even if you navigate back. The same happens on iOS.
One way to prevent this can be to check if indices are present in the array:
struct NewView: View {
@ObservedObject var new_view_settings: Settings
@State var index = -1
var body: some View {
VStack {
Button(action: {
//self.index = self.new_view_settings.count - 1
}) {
Text("change index")
}
// check if `index` is in array
if self.index > -1 && self.index < self.new_view_settings.data.count {
Text("\(self.new_view_settings.data[index])")
}
}
}
}
Note: in general I don't recommend dealing with indices in SwiftUI views - there usually is a better way to pass data. Dealing with indices is risky.