In this case, @State
is behaving exactly like it is supposed to: as a persistent store for the component it's attached to.
Fundamentally, pushing a view with a NavigationLink is like any other component in the view hierarchy. Whether or not the screen is actually visible is an implementation detail. While SwiftUI is not actually rendering your hidden UI elements after closing a screen, it does hold on to the View
tree.
You can force a view to be completely thrown away with the .id(_:)
modifier, for example:
struct ContentView: View {
@State var i = 0
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView().id(i)) {
Text("Show Detail View")
}
Button("Toggle") {
self.i += 1
UserDefaults.standard.set(
!UserDefaults.standard.bool(forKey: "JustKey"),
forKey: "JustKey")
}
}
}
}
}
The toggle button both modifies the value for JustKey and increments the value that we pass to .id(i)
. When the singular argument to id(_:)
changes, the id modifier tells SwiftUI that this is a different view, so when SwiftUI runs it's diffing algorithm it throws away the old one and creates a new one (with new @State
variables). Read more about id here.
The explanation above provides a workaround, but it's not a good solution, IMO. A much better solution is to use an ObservableObject
. It looks like this:
@ObservedObject condition = KeyPathObserver(\.JustKey, on: UserDefaults.standard)
You can find the code that implements the KeyPathObserver
and a full working Playground example at this SO answer