We've heard through the grapevine from a SwiftUI engineer (oh how I wish we could hear back through the Feedback app so we didn't have to depend on prior social connections) that this was made worse by a known regression in iOS 16.1 beta which is expected to be fixed before 16.1 ships.
It was explained that there are actually two bugs being illustrated here in various ways and one has been around for the entire life of SwiftUI!
The longstanding SwiftUI bug is that under certain circumstances a ViewBuilder does not correctly create a dependency on @State data which means that when the data changes it might not realize it needs to update the view hierarchy again. This can cause captured ViewBuilder closures to operate on stale data. If I understand correctly, this primarily happens when a @State variable is being depended on in a closure and never as a bare parameter to a view. In this example, that applies to the data variable. We were told they really want to fix this bug, however I got the impression it's not going to be fixed soon - certainly not in the iOS 16.1 timeframe. If you hit this bug, it can be worked around by making sure your @State variable is used in the view hierarchy somewhere as a direct input to another view or view modifier somehow and not just inside the body of a ViewBuilder closure.
The second bug is that as of iOS 16.1 beta, the navigationDestination is apparently sometimes hanging on to a stale version of the closure. I believe that explains why in this example the final printout is "data: 0" instead of "data: 3". Our understanding is that this bug is fixed internally and it sounds like it's expected it will land before 16.1 actually ships.
In the meantime, I was able to workaround both of these bugs by using a clever bit of indirection. Moving the data array into an object was enough:
class WorkaroundDataHolder: ObservableObject {
@Published var data: [String] = []
}
struct ContentView: View {
@StateObject var workaround = WorkaroundDataHolder()
@State var navPath = NavigationPath()
var body: some View {
NavigationStack(path: $navPath) {
Button("Hit It") {
print("click")
navPath.append(0)
}
.navigationDestination(for: Int.self) { index in
let _ = print("index: \(index) data: \(workaround.data.count)")
if workaround.data.indices.contains(index) {
Text(workaround.data[index])
} else {
Text("Not found.")
}
}
}
.task {
workaround.data = ["Item 1", "Item 2", "Item 3"]
}
}
}